- Generell sind Fragen an dich in den Chunks (glaub auch nur beim Data Load) mit ??? gekennzeichnet, erleichter die Suche ;)
- Sixtrix hat nur sehr wenige Datenpunkte/Keywords (2,368/10,000) -> ist halt so, wird dann vermerkt.
- Data cleaning angepasst - columns nun wie gewünscht
- Dadurch ergeben sich zwei Probleme:
-> Die Lollipos ergeben jetzt leider für Difficulty keinen Sinn mehr, da GKP das nicht ausgibt und somit kein Vergleich möglich ist. Schade. Eine andere baseline? Oder hast du eine andere Idee? -> SECockpit isnd die einzigen mit einem Difficulty Maximum über 100, also anscheinend eine andere range… Hab auf die Schnelle kein maximal möglichen Wert gefunden und daher das max(diff) als 100% gesetzt.
- Bisher unterscheiden sich die Farben der Engine zwischen Ch. 2 und Ch. 3, 4 & 5. Auch generell nicht so simpel, da ja einige bei Ch. 2 zwei Mal auftauchen (SEMrush, Ahrefs). Kann das so zum Kunden oder soll ich das fixen? Im finalen dann sicher, aber evtl. bekommen wir das Problem ja gar nicht…?
## save plots?
#save <- TRUE
save <- FALSE
## packages
required_packages <- c("tidyverse", "readxl", "ggthemes", "hrbrthemes", "extrafont", "plotly", "scales", "stringr", "gganimate", "here", "tidytext", "sentimentr", "scales", "DT", "here", "sm", "mblm", "prettydoc", "reshape2", "treemapify", "glue", "magick", "imager", "fs", "knitr", "DataExplorer", "inspectdf", "rmdformats", "prettydoc", "janitor", "showtext", "ggbeeswarm", "ggtext", "kableExtra", "rcartocolor", "ggsci", "patchwork")
for(i in required_packages) {
if(!require(i, character.only = T)) {
# if package is not existing, install then load the package
install.packages(i, dependencies = T)
library(i, character.only = T)
}
}
## theme updates
font_add_google("Montserrat", "Montserrat")
theme_set(ggthemes::theme_clean(base_size = 15, base_family = "Montserrat"))
theme_update(plot.background = element_rect(color = NA),
#panel.background = element_rect(color = "grey20", size = .4), ## turn off if lighter version
plot.title = element_markdown(size = 18, hjust = .5, face = "plain"),
plot.subtitle = element_text(size = 12, hjust = .5, face = "plain"),
axis.line.x = element_line(color = "grey20"), ## turn off if full panel border
axis.line.y = element_line(color = "grey20"), ## turn off if full panel border
#axis.line.x = element_blank(), axis.line.y = element_blank(), ## turn off if lighter version
axis.ticks = element_line(color = "grey20", size = .3),
axis.title.x = element_text(face = "plain", margin = margin(t = 10)),
axis.title.y = element_text(face = "plain", margin = margin(r = 10)),
axis.text = element_text(color = "grey20"),
legend.title = element_text(color = "grey33"),
legend.text = element_text(family = "Montserrat", size = 10, color = "grey33"),
legend.background = element_rect(fill = NA, color = NA))
## theme settings for flipped plots
theme_flip <-
theme(panel.grid.major.x = element_line(colour = "gray", linetype = "dotted", size = .25),
panel.grid.major.y = element_blank(),
axis.title = element_text(face = "bold"),
axis.text.y = element_text(face = "bold"),
legend.position = "bottom")
## theme settings for facet plots
theme_facet <-
theme(axis.text.y = element_text(face = "plain", size = 10),
axis.text.x = element_text(size = 8),
strip.text = element_text(face = "bold"),
plot.margin = margin(10, 10, 10, 25),
panel.spacing = unit(1, "lines"))
## numeric format for labels
num_format <- format_format(big.mark = ",", small.mark = ",", scientific = F)There is a wealth of Software-as-a-Service (SaaS) companies today that offer a range of keyword analytics data, such as keyword search volume and keyword difficulty scores. For the end user, it is often challenging to make informed comparisons between different data providers, which may be skewed or misleading when viewed individually.
This large-scale analysis of Keyword Analytics data aims to compare data across various SEO tools.More specificially we will do a comparision of the following variables:
We compare the data of a total of 10 tools:
## path to processed data
rmd_tools <- here("proc_data", "tools.Rmd")
## if data is not available, load, clean and bind datasets
if(!file.exists(rmd_tools)){
### load data #################################################################
## Google Keyword Planner Data (Tool #1)
df_gkp_raw <- read_csv(here("raw_data", "Step3_Tools_data", "1_Google_keyword_Planer_basis", "GKP_data_final_split.csv"))
## Ahrefs Data (Tool #2)
df_ahr_raw <- read_csv(here("raw_data", "Step3_Tools_data", "2_Ahrefs", "export", "ahrefs_export.csv"))
## contains less rows than GKP data as the tool does not have enough data points. Should go into the analysis.
## Moz (Tool #3)
path <- here("raw_data", "Step3_Tools_data", "3_Moz", "export")
file_names_csv <- list.files(path = path, recursive = T, full.names = T)
file <- map(file_names_csv,
function(x) read_csv(x, skip = 13) %>%
mutate(`Min Volume` = as.numeric(`Min Volume`),
`Max Volume` = as.numeric(`Max Volume`)))
names(file) <- gsub(".csv","", list.files(path, full.names = F), fixed = T)
df_moz_raw <-
bind_rows(file, .id = "column_name") %>%
clean_names()
## SEMRush (Tool #4)
path <- here("raw_data", "Step3_Tools_data", "4_SemRush", "export")
file_names_csv <- list.files(path = path, recursive = T, full.names = T)
file <- map(file_names_csv, read_csv)
names(file) <- gsub(".csv","", list.files(path, full.names = F), fixed = T)
df_sem_raw <-
bind_rows(file, .id = "column_name") %>%
clean_names()
## KeywordTool.io (Tool #5)
path <- here("raw_data", "Step3_Tools_data", "5_Keywordtool", "export")
file_names_csv <- list.files(path = path, recursive = T, full.names = T)
file <- map(file_names_csv, read_csv)
names(file) <- gsub(".csv","", list.files(path, full.names = F), fixed = T)
df_kwt_raw <-
bind_rows(file, .id = "column_name") %>%
clean_names()
## KWFinder (Tool #6)
path <- here("raw_data", "Step3_Tools_data", "6_KWfinder", "export")
file_names_csv <- list.files(path = path, recursive = T, full.names = T)
file <- map(file_names_csv, read_csv)
names(file) <- gsub(".csv","", list.files(path, full.names = F), fixed = T)
df_kwf_raw <-
bind_rows(file, .id = "column_name") %>%
clean_names()
## LongtailPro (Tool #7)
path <- here("raw_data", "Step3_Tools_data", "7_LongtailPro", "export")
file_names_csv <- list.files(path = path, recursive = T, full.names = T)
file <- map(file_names_csv, read_csv)
names(file) <- gsub(".csv","", list.files(path, full.names = F), fixed = T)
df_ltp_raw <-
bind_rows(file, .id = "column_name") %>%
clean_names()
## SECockpit (Tool #8)
path <- here("raw_data", "Step3_Tools_data", "8_SECockpit", "export")
file_names_csv <- list.files(path = path, recursive = T, full.names = T)
file <- map(file_names_csv, function(x) read_csv2(x))
names(file) <- gsub(".csv","", list.files(path, full.names = F), fixed = T)
df_sec_raw <-
bind_rows(file, .id = "column_name") %>%
clean_names()
## Sixtrix (Tool #9)
path <- here("raw_data", "Step3_Tools_data", "9_Sixtrix", "export")
file_names_csv <- list.files(path = path, recursive = T, full.names = T)
file <- map(file_names_csv, read_csv2)
names(file) <- gsub(".csv","", list.files(path, full.names = F), fixed = T)
df_six_raw <-
bind_rows(file, .id = "column_name") %>%
clean_names()
### clean data ################################################################
## select relevant columns and bind all tools
## Google Key Planner
df_gkp <-
df_gkp_raw %>%
mutate(
tool_id = 1,
tool = "Google Keyword Planner"
) %>%
dplyr::select(
tool_id, tool,
keyword,
volume = "avg_monthly_searches.x",
cpc_min = "top_of_page_bid_low_range.y",
cpc_max = "top_of_page_bid_high_range.y"
) %>%
mutate(
diff = NA_real_, ## GKP dies not have a difficulty score output
cpc = cpc_max - (cpc_max - cpc_min) / 2
) %>%
dplyr::select(-cpc_min, cpc_max)
## Ahrefs
df_ahr <-
df_ahr_raw %>%
mutate(
tool_id = 2,
tool = "Ahrefs"
) %>%
dplyr::select(
tool_id, tool,
keyword = "Keyword",
volume = "Volume",
diff = "Difficulty",
cpc = "CPC"
)
## Moz
df_moz <-
df_moz_raw %>%
mutate(
tool_id = 3,
tool = "Moz (mix of sources)"
) %>%
dplyr::select(
tool_id, tool,
keyword,
max_volume,
min_volume,
diff = "difficulty"
) %>%
mutate(
cpc = NA_real_, ## Moz has no CPC output
volume = round(max_volume - (max_volume - min_volume) / 2, 0)
) %>%
dplyr::select(-max_volume, -min_volume)
## SEMrush
df_sem <-
df_sem_raw %>%
mutate(
tool_id = 4,
tool = "SEMrush"
) %>%
dplyr::select(
tool_id, tool,
keyword,
volume,
diff = "keyword_difficulty",
cpc = "cpc_usd"
)
## KeywordTool.io
df_kwt <-
df_kwt_raw %>%
mutate(
tool_id = 5,
tool = "KeywordTool.io"
) %>%
dplyr::select(
tool_id, tool,
keyword = "keywords",
volume = "search_volume_average",
diff = "competition",
cpc = "cpc_usd"
)
## KWFinder
df_kwf <-
df_kwf_raw %>%
mutate(
tool_id = 6,
tool = "KWFinder"
) %>%
dplyr::select(
tool_id, tool,
keyword,
volume = "avg_search_volume",
diff = "keyword_difficulty",
cpc
)
df_ltp <-
df_ltp_raw %>%
mutate(
tool_id = 7,
tool = "LongtailPro"
) %>%
dplyr::select(
tool_id, tool,
keyword = "keywords",
volume,
diff = "avg_kc",
cpc = "bid"
) %>%
mutate(cpc = as.numeric(str_sub(cpc, 2)))
## SECockpit
df_sec <-
df_sec_raw %>%
mutate(
tool_id = 8,
tool = "SECockpit"
) %>%
dplyr::select(
tool_id, tool,
keyword = "phrase",
volume = "monthly_searches",
diff = "top_results",
cpc
) %>%
mutate(
cpc = as.numeric(cpc),
diff = diff / max(diff) * 100 ## ??? top_results has a range from 6 to 464. Not sure hwat the possible max is so I take 464 for now
)
## Sixtrix
df_six <-
df_six_raw %>%
mutate(
tool_id = 9,
tool = "Sixtrix"
) %>%
dplyr::select(
tool_id, tool,
keyword = "x_u_feff_keyword",
volume = "search_volume",
diff = "competition",
cpc
) %>%
mutate(
## extract max volume from range string
volume = str_replace(volume, ".*\\s", ""), ## using the max of the range here
volume = str_replace(volume, "\\.", ""),
thousands = if_else(str_detect(volume, ".*K"), T, F), ## dealing with "K" units
volume = str_replace(volume, "K", ""),
volume = if_else(thousands == T,
as.numeric(volume) * 1000,
as.numeric(volume)),
## turn difficulty into numbers
diff = as.numeric(diff), ## turns "-" to NA - guess that's good!
## clean cpc and convert to USD
cpc = str_replace(cpc, ",", "."),
cpc = str_sub(cpc, 1, -4),
cpc = as.numeric(cpc) * 1.11 ## EUR -> USD, exchange rate 2020-01-16 05:46 pm
) %>%
dplyr::select(-thousands)
### final dataset #############################################################
## join all tools
df_tools <-
df_gkp %>%
bind_rows(df_ahr) %>%
bind_rows(df_moz) %>%
bind_rows(df_sem) %>%
bind_rows(df_kwt) %>%
bind_rows(df_kwf) %>%
bind_rows(df_ltp) %>%
bind_rows(df_sec) %>%
bind_rows(df_six) %>%
mutate(
tool = factor(tool, levels = unique(tool)),
volume_cat = case_when(
volume <= 100 ~ "0-100",
volume > 100 & volume <= 1000 ~ "100-1000",
volume > 1000 & volume <= 10000 ~ "1000-10000",
volume > 100000 ~ "10000+",
)
) %>%
filter(cpc > 0) ## remove $0 "costs"
saveRDS(df_tools, file = rmd_tools)
}else{
df_tools <- readRDS(rmd_tools)
}
## long format with volume, difficulty + cpc as one column 'cateogry'
df_tools_long <-
df_tools %>%
dplyr::select(-tool_id, -volume_cat) %>%
pivot_longer(c(-tool, -keyword),
names_to = "category",
values_to = "value")## path to processed data
rmd_kws <- here("proc_data", "keyword_suggestions.Rmd")
## load version 1 csv
df_kw_raw <- read_csv(here("raw_data", "Step3_Tools_data", "keyword_suggestions", "keyword_suggestions_v1.csv")) %>%
clean_names()
if(!file.exists(rmd_kws)){
### clean data ################################################################
## Transform data into long format (two columns with *SEO tool* (`tool`)
## and *suggested keywords* (`suggested`))
df_kw <-
df_kw_raw %>%
mutate_at(vars(se_mrush_broad_match:majestic), as.numeric) %>%
pivot_longer(
cols = google_keyword_planner:majestic,
names_to = "tool",
values_to = "suggested"
) %>%
## remove not avilable values
filter(!is.na(suggested)) %>%
## remove combination of keyword search for now (since not all provide it)
filter(!str_detect(keyword, "All 5 KW")) %>%
## Clean SEO tool names
mutate(tool =
factor(
tool,
levels = c("google_keyword_planner",
"ahrefs_all_keyword_ideas",
"ahrefs_phrase_match",
"se_mrush_broad_match",
"se_mrush_phrase_match",
"moz_include_a_mix_of_sources",
"ubersuggest_related",
"se_cockpit",
"keyword_tool_io",
"kw_finder"),
labels = c("Google Keyword Planner",
"Ahrefs (all keyword ideas)",
"Ahrefs (phrase match)",
"SEMrush (broad match)",
"SEMrush (phrase match)",
"Moz (mix of sources)",
"Ubersuggest (related)",
"SECockpit",
"KeywordTool.io",
"KWFinder")
)
) %>%
## Sort SEO tools and keyword categories by mean number of suggestions
group_by(tool) %>%
mutate(tool_median = median(suggested)) %>%
group_by(category) %>%
mutate(category_median = median(suggested)) %>%
ungroup() %>%
mutate(
tool_fct = fct_reorder(tool, tool_median),
category_fct = fct_reorder(category, category_median),
)
### final dataset ########################################################
saveRDS(df_kw, file = rmd_kws)
}else{
df_kw <- readRDS(rmd_kws)
}
## range of suggestions
range_kw <- range(df_kw$suggested)
## number of keywords per cateory
## (based on the un-cleanded data since we have removed NAs)
n_kw_cat <-
df_kw_raw %>%
group_by(category) %>%
count() %>%
ungroup() %>%
summarize(n = unique(n)) %>%
pull(n)There are 7 SEO tools, each used to search 75 keywords. The keywords fall into 15 categories with each category containing 6. Minimum of suggestions was 22 and the highest values was 6.58880510^{6}.
## table
df_kw %>%
dplyr::select(
Category = "category",
Keyword = "keyword",
`SEO Tool` = "tool",
Suggestions = "suggested",
`Median per Tool` = "tool_median",
`Median per Category` = "category_median"
) %>%
kable(format.args = list(big.mark = ",",
small.mark = ",",
decimal.mark = ".",
scientific = F)) %>%
kable_styling(fixed_thead = T) %>%
collapse_rows(columns = 1:2, valign = "top") %>%
scroll_box(width = "100%", height = "600px")| Category | Keyword | SEO Tool | Suggestions | Median per Tool | Median per Category |
|---|---|---|---|---|---|
| Financial Services | finance | Google Keyword Planner | 691 | 691 | 153,308.0 |
| Ahrefs (all keyword ideas) | 1,269,225 | 43,488 | 153,308.0 | ||
| Ahrefs (phrase match) | 1,233,201 | 23,021 | 153,308.0 | ||
| SEMrush (broad match) | 1,201,273 | 36,523 | 153,308.0 | ||
| SEMrush (phrase match) | 973,555 | 25,475 | 153,308.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 153,308.0 | ||
| Ubersuggest (related) | 196,562 | 4,261 | 153,308.0 | ||
| investing | Google Keyword Planner | 2,528 | 691 | 153,308.0 | |
| Ahrefs (all keyword ideas) | 264,668 | 43,488 | 153,308.0 | ||
| Ahrefs (phrase match) | 255,608 | 23,021 | 153,308.0 | ||
| SEMrush (broad match) | 1,209,214 | 36,523 | 153,308.0 | ||
| SEMrush (phrase match) | 135,075 | 25,475 | 153,308.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 153,308.0 | ||
| Ubersuggest (related) | 29,295 | 4,261 | 153,308.0 | ||
| loans | Google Keyword Planner | 936 | 691 | 153,308.0 | |
| Ahrefs (all keyword ideas) | 1,007,861 | 43,488 | 153,308.0 | ||
| Ahrefs (phrase match) | 994,674 | 23,021 | 153,308.0 | ||
| SEMrush (broad match) | 1,851,628 | 36,523 | 153,308.0 | ||
| SEMrush (phrase match) | 541,163 | 25,475 | 153,308.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 153,308.0 | ||
| Ubersuggest (related) | 153,308 | 4,261 | 153,308.0 | ||
| insurance | Google Keyword Planner | 789 | 691 | 153,308.0 | |
| Ahrefs (all keyword ideas) | 6,588,805 | 43,488 | 153,308.0 | ||
| Ahrefs (phrase match) | 6,522,620 | 23,021 | 153,308.0 | ||
| SEMrush (broad match) | 4,109,928 | 36,523 | 153,308.0 | ||
| SEMrush (phrase match) | 4,003,105 | 25,475 | 153,308.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 153,308.0 | ||
| Ubersuggest (related) | 823,235 | 4,261 | 153,308.0 | ||
| financial advisor | Google Keyword Planner | 695 | 691 | 153,308.0 | |
| Ahrefs (all keyword ideas) | 74,293 | 43,488 | 153,308.0 | ||
| Ahrefs (phrase match) | 68,696 | 23,021 | 153,308.0 | ||
| SEMrush (broad match) | 59,695 | 36,523 | 153,308.0 | ||
| SEMrush (phrase match) | 41,971 | 25,475 | 153,308.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 153,308.0 | ||
| Ubersuggest (related) | 9,373 | 4,261 | 153,308.0 | ||
| Diet | how to lose weight | Google Keyword Planner | 1,648 | 691 | 10,055.0 |
| Ahrefs (all keyword ideas) | 92,045 | 43,488 | 10,055.0 | ||
| Ahrefs (phrase match) | 30,342 | 23,021 | 10,055.0 | ||
| SEMrush (broad match) | 45,852 | 36,523 | 10,055.0 | ||
| SEMrush (phrase match) | 43,474 | 25,475 | 10,055.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,055.0 | ||
| Ubersuggest (related) | 10,055 | 4,261 | 10,055.0 | ||
| low carb | Google Keyword Planner | 1,378 | 691 | 10,055.0 | |
| Ahrefs (all keyword ideas) | 319,292 | 43,488 | 10,055.0 | ||
| Ahrefs (phrase match) | 296,019 | 23,021 | 10,055.0 | ||
| SEMrush (broad match) | 188,911 | 36,523 | 10,055.0 | ||
| SEMrush (phrase match) | 180,554 | 25,475 | 10,055.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,055.0 | ||
| Ubersuggest (related) | 40,634 | 4,261 | 10,055.0 | ||
| nutrition | Google Keyword Planner | 1,025 | 691 | 10,055.0 | |
| Ahrefs (all keyword ideas) | 1,490,264 | 43,488 | 10,055.0 | ||
| Ahrefs (phrase match) | 1,481,436 | 23,021 | 10,055.0 | ||
| SEMrush (broad match) | 1,317,219 | 36,523 | 10,055.0 | ||
| SEMrush (phrase match) | 1,112,682 | 25,475 | 10,055.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,055.0 | ||
| Ubersuggest (related) | 223,859 | 4,261 | 10,055.0 | ||
| diet plans | Google Keyword Planner | 1,766 | 691 | 10,055.0 | |
| Ahrefs (all keyword ideas) | 29,129 | 43,488 | 10,055.0 | ||
| Ahrefs (phrase match) | 6,850 | 23,021 | 10,055.0 | ||
| SEMrush (broad match) | 85,786 | 36,523 | 10,055.0 | ||
| SEMrush (phrase match) | 4,025 | 25,475 | 10,055.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,055.0 | ||
| Ubersuggest (related) | 1,693 | 4,261 | 10,055.0 | ||
| weight loss tips | Google Keyword Planner | 907 | 691 | 10,055.0 | |
| Ahrefs (all keyword ideas) | 17,659 | 43,488 | 10,055.0 | ||
| Ahrefs (phrase match) | 2,828 | 23,021 | 10,055.0 | ||
| SEMrush (broad match) | 3,865 | 36,523 | 10,055.0 | ||
| SEMrush (phrase match) | 3,756 | 25,475 | 10,055.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,055.0 | ||
| Ubersuggest (related) | 1,787 | 4,261 | 10,055.0 | ||
| Online Marketing | marketing | Google Keyword Planner | 284 | 691 | 34,153.0 |
| Ahrefs (all keyword ideas) | 2,285,404 | 43,488 | 34,153.0 | ||
| Ahrefs (phrase match) | 2,281,425 | 23,021 | 34,153.0 | ||
| SEMrush (broad match) | 5,068,178 | 36,523 | 34,153.0 | ||
| SEMrush (phrase match) | 1,544,835 | 25,475 | 34,153.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 34,153.0 | ||
| Ubersuggest (related) | 378,278 | 4,261 | 34,153.0 | ||
| seo | Google Keyword Planner | 428 | 691 | 34,153.0 | |
| Ahrefs (all keyword ideas) | 362,649 | 43,488 | 34,153.0 | ||
| Ahrefs (phrase match) | 351,981 | 23,021 | 34,153.0 | ||
| SEMrush (broad match) | 234,768 | 36,523 | 34,153.0 | ||
| SEMrush (phrase match) | 229,065 | 25,475 | 34,153.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 34,153.0 | ||
| Ubersuggest (related) | 77,664 | 4,261 | 34,153.0 | ||
| digital marketing | Google Keyword Planner | 377 | 691 | 34,153.0 | |
| Ahrefs (all keyword ideas) | 127,183 | 43,488 | 34,153.0 | ||
| Ahrefs (phrase match) | 106,089 | 23,021 | 34,153.0 | ||
| SEMrush (broad match) | 102,613 | 36,523 | 34,153.0 | ||
| SEMrush (phrase match) | 96,133 | 25,475 | 34,153.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 34,153.0 | ||
| Ubersuggest (related) | 30,000 | 4,261 | 34,153.0 | ||
| affiliate marketing | Google Keyword Planner | 1,206 | 691 | 34,153.0 | |
| Ahrefs (all keyword ideas) | 31,233 | 43,488 | 34,153.0 | ||
| Ahrefs (phrase match) | 25,350 | 23,021 | 34,153.0 | ||
| SEMrush (broad match) | 18,828 | 36,523 | 34,153.0 | ||
| SEMrush (phrase match) | 17,065 | 25,475 | 34,153.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 34,153.0 | ||
| Ubersuggest (related) | 6,128 | 4,261 | 34,153.0 | ||
| marketing plan | Google Keyword Planner | 543 | 691 | 34,153.0 | |
| Ahrefs (all keyword ideas) | 54,207 | 43,488 | 34,153.0 | ||
| Ahrefs (phrase match) | 41,784 | 23,021 | 34,153.0 | ||
| SEMrush (broad match) | 44,816 | 36,523 | 34,153.0 | ||
| SEMrush (phrase match) | 34,153 | 25,475 | 34,153.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 34,153.0 | ||
| Ubersuggest (related) | 8,840 | 4,261 | 34,153.0 | ||
| Women Fashion | dresses | Google Keyword Planner | 1,545 | 691 | 39,866.0 |
| Ahrefs (all keyword ideas) | 1,337,414 | 43,488 | 39,866.0 | ||
| Ahrefs (phrase match) | 1,240,915 | 23,021 | 39,866.0 | ||
| SEMrush (broad match) | 4,447,697 | 36,523 | 39,866.0 | ||
| SEMrush (phrase match) | 767,753 | 25,475 | 39,866.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 39,866.0 | ||
| Ubersuggest (related) | 293,656 | 4,261 | 39,866.0 | ||
| clothes | Google Keyword Planner | 2,336 | 691 | 39,866.0 | |
| Ahrefs (all keyword ideas) | 2,019,714 | 43,488 | 39,866.0 | ||
| Ahrefs (phrase match) | 1,991,817 | 23,021 | 39,866.0 | ||
| SEMrush (broad match) | 2,886,463 | 36,523 | 39,866.0 | ||
| SEMrush (phrase match) | 1,076,138 | 25,475 | 39,866.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 39,866.0 | ||
| Ubersuggest (related) | 272,112 | 4,261 | 39,866.0 | ||
| fashion | Google Keyword Planner | 1,290 | 691 | 39,866.0 | |
| Ahrefs (all keyword ideas) | 1,856,785 | 43,488 | 39,866.0 | ||
| Ahrefs (phrase match) | 1,805,903 | 23,021 | 39,866.0 | ||
| SEMrush (broad match) | 1,386,122 | 36,523 | 39,866.0 | ||
| SEMrush (phrase match) | 1,224,626 | 25,475 | 39,866.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 39,866.0 | ||
| Ubersuggest (related) | 310,343 | 4,261 | 39,866.0 | ||
| cocktail dresses | Google Keyword Planner | 1,600 | 691 | 39,866.0 | |
| Ahrefs (all keyword ideas) | 39,866 | 43,488 | 39,866.0 | ||
| Ahrefs (phrase match) | 14,936 | 23,021 | 39,866.0 | ||
| SEMrush (broad match) | 34,840 | 36,523 | 39,866.0 | ||
| SEMrush (phrase match) | 13,029 | 25,475 | 39,866.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 39,866.0 | ||
| Ubersuggest (related) | 4,216 | 4,261 | 39,866.0 | ||
| plus size dresses | Google Keyword Planner | 1,884 | 691 | 39,866.0 | |
| Ahrefs (all keyword ideas) | 100,985 | 43,488 | 39,866.0 | ||
| Ahrefs (phrase match) | 9,935 | 23,021 | 39,866.0 | ||
| SEMrush (broad match) | 60,499 | 36,523 | 39,866.0 | ||
| SEMrush (phrase match) | 27,851 | 25,475 | 39,866.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 39,866.0 | ||
| Ubersuggest (related) | 2,520 | 4,261 | 39,866.0 | ||
| Home Improvement | construction | Google Keyword Planner | 369 | 691 | 20,392.0 |
| Ahrefs (all keyword ideas) | 2,474,705 | 43,488 | 20,392.0 | ||
| Ahrefs (phrase match) | 2,471,944 | 23,021 | 20,392.0 | ||
| SEMrush (broad match) | 1,964,290 | 36,523 | 20,392.0 | ||
| SEMrush (phrase match) | 1,782,879 | 25,475 | 20,392.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 20,392.0 | ||
| Ubersuggest (related) | 292,258 | 4,261 | 20,392.0 | ||
| renovation | Google Keyword Planner | 369 | 691 | 20,392.0 | |
| Ahrefs (all keyword ideas) | 136,107 | 43,488 | 20,392.0 | ||
| Ahrefs (phrase match) | 133,467 | 23,021 | 20,392.0 | ||
| SEMrush (broad match) | 166,948 | 36,523 | 20,392.0 | ||
| SEMrush (phrase match) | 96,176 | 25,475 | 20,392.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 20,392.0 | ||
| Ubersuggest (related) | 21,632 | 4,261 | 20,392.0 | ||
| bathroom remodel | Google Keyword Planner | 677 | 691 | 20,392.0 | |
| Ahrefs (all keyword ideas) | 25,836 | 43,488 | 20,392.0 | ||
| Ahrefs (phrase match) | 15,058 | 23,021 | 20,392.0 | ||
| SEMrush (broad match) | 20,392 | 36,523 | 20,392.0 | ||
| SEMrush (phrase match) | 12,416 | 25,475 | 20,392.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 20,392.0 | ||
| Ubersuggest (related) | 3,241 | 4,261 | 20,392.0 | ||
| kitchen remodel | Google Keyword Planner | 653 | 691 | 20,392.0 | |
| Ahrefs (all keyword ideas) | 30,068 | 43,488 | 20,392.0 | ||
| Ahrefs (phrase match) | 15,710 | 23,021 | 20,392.0 | ||
| SEMrush (broad match) | 21,215 | 36,523 | 20,392.0 | ||
| SEMrush (phrase match) | 12,296 | 25,475 | 20,392.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 20,392.0 | ||
| Ubersuggest (related) | 3,358 | 4,261 | 20,392.0 | ||
| general contractor | Google Keyword Planner | 451 | 691 | 20,392.0 | |
| Ahrefs (all keyword ideas) | 50,817 | 43,488 | 20,392.0 | ||
| Ahrefs (phrase match) | 47,254 | 23,021 | 20,392.0 | ||
| SEMrush (broad match) | 42,249 | 36,523 | 20,392.0 | ||
| SEMrush (phrase match) | 23,039 | 25,475 | 20,392.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 20,392.0 | ||
| Ubersuggest (related) | 4,261 | 4,261 | 20,392.0 | ||
| Gardening | landscaping | Google Keyword Planner | 464 | 691 | 8,082.0 |
| Ahrefs (all keyword ideas) | 406,570 | 43,488 | 8,082.0 | ||
| Ahrefs (phrase match) | 403,114 | 23,021 | 8,082.0 | ||
| SEMrush (broad match) | 767,343 | 36,523 | 8,082.0 | ||
| SEMrush (phrase match) | 263,772 | 25,475 | 8,082.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 8,082.0 | ||
| Ubersuggest (related) | 60,255 | 4,261 | 8,082.0 | ||
| landscape design | Google Keyword Planner | 621 | 691 | 8,082.0 | |
| Ahrefs (all keyword ideas) | 42,455 | 43,488 | 8,082.0 | ||
| Ahrefs (phrase match) | 28,504 | 23,021 | 8,082.0 | ||
| SEMrush (broad match) | 35,538 | 36,523 | 8,082.0 | ||
| SEMrush (phrase match) | 26,587 | 25,475 | 8,082.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 8,082.0 | ||
| Ubersuggest (related) | 6,295 | 4,261 | 8,082.0 | ||
| garden centre | Google Keyword Planner | 182 | 691 | 8,082.0 | |
| Ahrefs (all keyword ideas) | 37,782 | 43,488 | 8,082.0 | ||
| Ahrefs (phrase match) | 8,001 | 23,021 | 8,082.0 | ||
| SEMrush (broad match) | 53,150 | 36,523 | 8,082.0 | ||
| SEMrush (phrase match) | 47,092 | 25,475 | 8,082.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 8,082.0 | ||
| Ubersuggest (related) | 8,082 | 4,261 | 8,082.0 | ||
| gardening tools | Google Keyword Planner | 1,622 | 691 | 8,082.0 | |
| Ahrefs (all keyword ideas) | 6,037 | 43,488 | 8,082.0 | ||
| Ahrefs (phrase match) | 3,267 | 23,021 | 8,082.0 | ||
| SEMrush (broad match) | 16,215 | 36,523 | 8,082.0 | ||
| SEMrush (phrase match) | 2,016 | 25,475 | 8,082.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 8,082.0 | ||
| Ubersuggest (related) | 900 | 4,261 | 8,082.0 | ||
| plant nursery | Google Keyword Planner | 1,342 | 691 | 8,082.0 | |
| Ahrefs (all keyword ideas) | 33,764 | 43,488 | 8,082.0 | ||
| Ahrefs (phrase match) | 18,008 | 23,021 | 8,082.0 | ||
| SEMrush (broad match) | 26,947 | 36,523 | 8,082.0 | ||
| SEMrush (phrase match) | 18,566 | 25,475 | 8,082.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 8,082.0 | ||
| Ubersuggest (related) | 3,720 | 4,261 | 8,082.0 | ||
| Vacations Travel | travel agency | Google Keyword Planner | 554 | 691 | 16,370.0 |
| Ahrefs (all keyword ideas) | 86,272 | 43,488 | 16,370.0 | ||
| Ahrefs (phrase match) | 64,928 | 23,021 | 16,370.0 | ||
| SEMrush (broad match) | 70,617 | 36,523 | 16,370.0 | ||
| SEMrush (phrase match) | 61,296 | 25,475 | 16,370.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 16,370.0 | ||
| Ubersuggest (related) | 19,655 | 4,261 | 16,370.0 | ||
| all inclusive resorts | Google Keyword Planner | 706 | 691 | 16,370.0 | |
| Ahrefs (all keyword ideas) | 82,001 | 43,488 | 16,370.0 | ||
| Ahrefs (phrase match) | 39,679 | 23,021 | 16,370.0 | ||
| SEMrush (broad match) | 48,465 | 36,523 | 16,370.0 | ||
| SEMrush (phrase match) | 31,456 | 25,475 | 16,370.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 16,370.0 | ||
| Ubersuggest (related) | 11,771 | 4,261 | 16,370.0 | ||
| vacation packages | Google Keyword Planner | 709 | 691 | 16,370.0 | |
| Ahrefs (all keyword ideas) | 106,259 | 43,488 | 16,370.0 | ||
| Ahrefs (phrase match) | 52,750 | 23,021 | 16,370.0 | ||
| SEMrush (broad match) | 45,007 | 36,523 | 16,370.0 | ||
| SEMrush (phrase match) | 36,567 | 25,475 | 16,370.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 16,370.0 | ||
| Ubersuggest (related) | 19,248 | 4,261 | 16,370.0 | ||
| cheap vacations | Google Keyword Planner | 1,145 | 691 | 16,370.0 | |
| Ahrefs (all keyword ideas) | 44,657 | 43,488 | 16,370.0 | ||
| Ahrefs (phrase match) | 4,524 | 23,021 | 16,370.0 | ||
| SEMrush (broad match) | 17,757 | 36,523 | 16,370.0 | ||
| SEMrush (phrase match) | 7,692 | 25,475 | 16,370.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 16,370.0 | ||
| Ubersuggest (related) | 1,254 | 4,261 | 16,370.0 | ||
| travel deals | Google Keyword Planner | 1,088 | 691 | 16,370.0 | |
| Ahrefs (all keyword ideas) | 49,181 | 43,488 | 16,370.0 | ||
| Ahrefs (phrase match) | 15,414 | 23,021 | 16,370.0 | ||
| SEMrush (broad match) | 16,370 | 36,523 | 16,370.0 | ||
| SEMrush (phrase match) | 14,497 | 25,475 | 16,370.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 16,370.0 | ||
| Ubersuggest (related) | 5,631 | 4,261 | 16,370.0 | ||
| Legal | legal aid | Google Keyword Planner | 326 | 691 | 10,094.0 |
| Ahrefs (all keyword ideas) | 73,499 | 43,488 | 10,094.0 | ||
| Ahrefs (phrase match) | 55,473 | 23,021 | 10,094.0 | ||
| SEMrush (broad match) | 45,987 | 36,523 | 10,094.0 | ||
| SEMrush (phrase match) | 44,703 | 25,475 | 10,094.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,094.0 | ||
| Ubersuggest (related) | 10,114 | 4,261 | 10,094.0 | ||
| law firm | Google Keyword Planner | 342 | 691 | 10,094.0 | |
| Ahrefs (all keyword ideas) | 294,866 | 43,488 | 10,094.0 | ||
| Ahrefs (phrase match) | 289,472 | 23,021 | 10,094.0 | ||
| SEMrush (broad match) | 276,454 | 36,523 | 10,094.0 | ||
| SEMrush (phrase match) | 224,078 | 25,475 | 10,094.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,094.0 | ||
| Ubersuggest (related) | 35,696 | 4,261 | 10,094.0 | ||
| legal separation | Google Keyword Planner | 99 | 691 | 10,094.0 | |
| Ahrefs (all keyword ideas) | 14,505 | 43,488 | 10,094.0 | ||
| Ahrefs (phrase match) | 11,141 | 23,021 | 10,094.0 | ||
| SEMrush (broad match) | 10,094 | 36,523 | 10,094.0 | ||
| SEMrush (phrase match) | 7,075 | 25,475 | 10,094.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,094.0 | ||
| Ubersuggest (related) | 1,532 | 4,261 | 10,094.0 | ||
| attorney at law | Google Keyword Planner | 788 | 691 | 10,094.0 | |
| Ahrefs (all keyword ideas) | 38,683 | 43,488 | 10,094.0 | ||
| Ahrefs (phrase match) | 35,483 | 23,021 | 10,094.0 | ||
| SEMrush (broad match) | 23,509 | 36,523 | 10,094.0 | ||
| SEMrush (phrase match) | 19,083 | 25,475 | 10,094.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,094.0 | ||
| Ubersuggest (related) | 1,311 | 4,261 | 10,094.0 | ||
| free legal advice | Google Keyword Planner | 589 | 691 | 10,094.0 | |
| Ahrefs (all keyword ideas) | 15,837 | 43,488 | 10,094.0 | ||
| Ahrefs (phrase match) | 6,001 | 23,021 | 10,094.0 | ||
| SEMrush (broad match) | 5,964 | 36,523 | 10,094.0 | ||
| SEMrush (phrase match) | 5,953 | 25,475 | 10,094.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 10,094.0 | ||
| Ubersuggest (related) | 1,397 | 4,261 | 10,094.0 | ||
| Office Supplies | office equipment | Google Keyword Planner | 680 | 691 | 1,434.0 |
| Ahrefs (all keyword ideas) | 17,639 | 43,488 | 1,434.0 | ||
| Ahrefs (phrase match) | 9,452 | 23,021 | 1,434.0 | ||
| SEMrush (broad match) | 8,414 | 36,523 | 1,434.0 | ||
| SEMrush (phrase match) | 7,679 | 25,475 | 1,434.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 1,434.0 | ||
| Ubersuggest (related) | 1,527 | 4,261 | 1,434.0 | ||
| stationery online | Google Keyword Planner | 157 | 691 | 1,434.0 | |
| Ahrefs (all keyword ideas) | 33,696 | 43,488 | 1,434.0 | ||
| Ahrefs (phrase match) | 717 | 23,021 | 1,434.0 | ||
| SEMrush (broad match) | 1,434 | 36,523 | 1,434.0 | ||
| SEMrush (phrase match) | 1,426 | 25,475 | 1,434.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 1,434.0 | ||
| Ubersuggest (related) | 432 | 4,261 | 1,434.0 | ||
| cute office supplies | Google Keyword Planner | 55 | 691 | 1,434.0 | |
| Ahrefs (all keyword ideas) | 2,946 | 43,488 | 1,434.0 | ||
| Ahrefs (phrase match) | 145 | 23,021 | 1,434.0 | ||
| SEMrush (broad match) | 158 | 36,523 | 1,434.0 | ||
| SEMrush (phrase match) | 146 | 25,475 | 1,434.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 1,434.0 | ||
| Ubersuggest (related) | 24 | 4,261 | 1,434.0 | ||
| office furniture | Google Keyword Planner | 1,940 | 691 | 1,434.0 | |
| Ahrefs (all keyword ideas) | 87,178 | 43,488 | 1,434.0 | ||
| Ahrefs (phrase match) | 54,994 | 23,021 | 1,434.0 | ||
| SEMrush (broad match) | 59,647 | 36,523 | 1,434.0 | ||
| SEMrush (phrase match) | 59,224 | 25,475 | 1,434.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 1,434.0 | ||
| Ubersuggest (related) | 18,078 | 4,261 | 1,434.0 | ||
| office supply store | Google Keyword Planner | 410 | 691 | 1,434.0 | |
| Ahrefs (all keyword ideas) | 10,032 | 43,488 | 1,434.0 | ||
| Ahrefs (phrase match) | 3,372 | 23,021 | 1,434.0 | ||
| SEMrush (broad match) | 4,107 | 36,523 | 1,434.0 | ||
| SEMrush (phrase match) | 1,974 | 25,475 | 1,434.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 1,434.0 | ||
| Ubersuggest (related) | 250 | 4,261 | 1,434.0 | ||
| Web Hosting | free web hosting | Google Keyword Planner | 1,401 | 691 | 2,053.0 |
| Ahrefs (all keyword ideas) | 14,315 | 43,488 | 2,053.0 | ||
| Ahrefs (phrase match) | 3,559 | 23,021 | 2,053.0 | ||
| SEMrush (broad match) | 4,315 | 36,523 | 2,053.0 | ||
| SEMrush (phrase match) | 3,972 | 25,475 | 2,053.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,053.0 | ||
| Ubersuggest (related) | 1,181 | 4,261 | 2,053.0 | ||
| dedicated server | Google Keyword Planner | 665 | 691 | 2,053.0 | |
| Ahrefs (all keyword ideas) | 60,702 | 43,488 | 2,053.0 | ||
| Ahrefs (phrase match) | 50,602 | 23,021 | 2,053.0 | ||
| SEMrush (broad match) | 28,980 | 36,523 | 2,053.0 | ||
| SEMrush (phrase match) | 25,485 | 25,475 | 2,053.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,053.0 | ||
| Ubersuggest (related) | 3,763 | 4,261 | 2,053.0 | ||
| best web hosting | Google Keyword Planner | 1,246 | 691 | 2,053.0 | |
| Ahrefs (all keyword ideas) | 11,931 | 43,488 | 2,053.0 | ||
| Ahrefs (phrase match) | 2,368 | 23,021 | 2,053.0 | ||
| SEMrush (broad match) | 3,358 | 36,523 | 2,053.0 | ||
| SEMrush (phrase match) | 2,902 | 25,475 | 2,053.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,053.0 | ||
| Ubersuggest (related) | 919 | 4,261 | 2,053.0 | ||
| domain name registration | Google Keyword Planner | 835 | 691 | 2,053.0 | |
| Ahrefs (all keyword ideas) | 33,962 | 43,488 | 2,053.0 | ||
| Ahrefs (phrase match) | 2,053 | 23,021 | 2,053.0 | ||
| SEMrush (broad match) | 2,103 | 36,523 | 2,053.0 | ||
| SEMrush (phrase match) | 1,943 | 25,475 | 2,053.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,053.0 | ||
| Ubersuggest (related) | 718 | 4,261 | 2,053.0 | ||
| cheap domains | Google Keyword Planner | 217 | 691 | 2,053.0 | |
| Ahrefs (all keyword ideas) | 10,757 | 43,488 | 2,053.0 | ||
| Ahrefs (phrase match) | 299 | 23,021 | 2,053.0 | ||
| SEMrush (broad match) | 2,288 | 36,523 | 2,053.0 | ||
| SEMrush (phrase match) | 429 | 25,475 | 2,053.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,053.0 | ||
| Ubersuggest (related) | 81 | 4,261 | 2,053.0 | ||
| Dating | free dating sites | Google Keyword Planner | 757 | 691 | 3,951.0 |
| Ahrefs (all keyword ideas) | 49,099 | 43,488 | 3,951.0 | ||
| Ahrefs (phrase match) | 6,838 | 23,021 | 3,951.0 | ||
| SEMrush (broad match) | 24,250 | 36,523 | 3,951.0 | ||
| SEMrush (phrase match) | 15,450 | 25,475 | 3,951.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 3,951.0 | ||
| Ubersuggest (related) | 1,946 | 4,261 | 3,951.0 | ||
| women seeking men | Google Keyword Planner | 243 | 691 | 3,951.0 | |
| Ahrefs (all keyword ideas) | 13,098 | 43,488 | 3,951.0 | ||
| Ahrefs (phrase match) | 7,788 | 23,021 | 3,951.0 | ||
| SEMrush (broad match) | 3,844 | 36,523 | 3,951.0 | ||
| SEMrush (phrase match) | 3,527 | 25,475 | 3,951.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 3,951.0 | ||
| Ubersuggest (related) | 842 | 4,261 | 3,951.0 | ||
| speed dating | Google Keyword Planner | 267 | 691 | 3,951.0 | |
| Ahrefs (all keyword ideas) | 17,715 | 43,488 | 3,951.0 | ||
| Ahrefs (phrase match) | 16,773 | 23,021 | 3,951.0 | ||
| SEMrush (broad match) | 31,566 | 36,523 | 3,951.0 | ||
| SEMrush (phrase match) | 29,304 | 25,475 | 3,951.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 3,951.0 | ||
| Ubersuggest (related) | 3,865 | 4,261 | 3,951.0 | ||
| interracial dating | Google Keyword Planner | 194 | 691 | 3,951.0 | |
| Ahrefs (all keyword ideas) | 8,300 | 43,488 | 3,951.0 | ||
| Ahrefs (phrase match) | 5,120 | 23,021 | 3,951.0 | ||
| SEMrush (broad match) | 3,951 | 36,523 | 3,951.0 | ||
| SEMrush (phrase match) | 3,751 | 25,475 | 3,951.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 3,951.0 | ||
| Ubersuggest (related) | 920 | 4,261 | 3,951.0 | ||
| matrimonial | Google Keyword Planner | 533 | 691 | 3,951.0 | |
| Ahrefs (all keyword ideas) | 24,420 | 43,488 | 3,951.0 | ||
| Ahrefs (phrase match) | 23,021 | 23,021 | 3,951.0 | ||
| SEMrush (broad match) | 47,687 | 36,523 | 3,951.0 | ||
| SEMrush (phrase match) | 15,633 | 25,475 | 3,951.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 3,951.0 | ||
| Ubersuggest (related) | 5,913 | 4,261 | 3,951.0 | ||
| Wedding | wedding cakes | Google Keyword Planner | 928 | 691 | 25,253.5 |
| Ahrefs (all keyword ideas) | 35,360 | 43,488 | 25,253.5 | ||
| Ahrefs (phrase match) | 31,459 | 23,021 | 25,253.5 | ||
| SEMrush (broad match) | 94,548 | 36,523 | 25,253.5 | ||
| SEMrush (phrase match) | 25,475 | 25,475 | 25,253.5 | ||
| Ubersuggest (related) | 7,030 | 4,261 | 25,253.5 | ||
| wedding invitations | Google Keyword Planner | 716 | 691 | 25,253.5 | |
| Ahrefs (all keyword ideas) | 71,946 | 43,488 | 25,253.5 | ||
| Ahrefs (phrase match) | 55,974 | 23,021 | 25,253.5 | ||
| SEMrush (broad match) | 90,872 | 36,523 | 25,253.5 | ||
| SEMrush (phrase match) | 41,568 | 25,475 | 25,253.5 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 25,253.5 | ||
| Ubersuggest (related) | 14,621 | 4,261 | 25,253.5 | ||
| wedding planner | Google Keyword Planner | 333 | 691 | 25,253.5 | |
| Ahrefs (all keyword ideas) | 42,023 | 43,488 | 25,253.5 | ||
| Ahrefs (phrase match) | 34,420 | 23,021 | 25,253.5 | ||
| SEMrush (broad match) | 36,523 | 36,523 | 25,253.5 | ||
| SEMrush (phrase match) | 28,913 | 25,475 | 25,253.5 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 25,253.5 | ||
| Ubersuggest (related) | 8,859 | 4,261 | 25,253.5 | ||
| bridal dresses | Google Keyword Planner | 1,390 | 691 | 25,253.5 | |
| Ahrefs (all keyword ideas) | 39,276 | 43,488 | 25,253.5 | ||
| Ahrefs (phrase match) | 7,634 | 23,021 | 25,253.5 | ||
| SEMrush (broad match) | 25,032 | 36,523 | 25,253.5 | ||
| SEMrush (phrase match) | 12,729 | 25,475 | 25,253.5 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 25,253.5 | ||
| Ubersuggest (related) | 2,490 | 4,261 | 25,253.5 | ||
| veil | Google Keyword Planner | 339 | 691 | 25,253.5 | |
| Ahrefs (all keyword ideas) | 166,008 | 43,488 | 25,253.5 | ||
| Ahrefs (phrase match) | 163,971 | 23,021 | 25,253.5 | ||
| SEMrush (broad match) | 119,340 | 36,523 | 25,253.5 | ||
| SEMrush (phrase match) | 96,817 | 25,475 | 25,253.5 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 25,253.5 | ||
| Ubersuggest (related) | 14,198 | 4,261 | 25,253.5 | ||
| Automotive | automatic cars | Google Keyword Planner | 600 | 691 | 5,811.0 |
| Ahrefs (all keyword ideas) | 11,634 | 43,488 | 5,811.0 | ||
| Ahrefs (phrase match) | 3,726 | 23,021 | 5,811.0 | ||
| SEMrush (broad match) | 27,619 | 36,523 | 5,811.0 | ||
| SEMrush (phrase match) | 9,603 | 25,475 | 5,811.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 5,811.0 | ||
| Ubersuggest (related) | 1,960 | 4,261 | 5,811.0 | ||
| motor car | Google Keyword Planner | 717 | 691 | 5,811.0 | |
| Ahrefs (all keyword ideas) | 43,488 | 43,488 | 5,811.0 | ||
| Ahrefs (phrase match) | 7,871 | 23,021 | 5,811.0 | ||
| SEMrush (broad match) | 18,265 | 36,523 | 5,811.0 | ||
| SEMrush (phrase match) | 18,265 | 25,475 | 5,811.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 5,811.0 | ||
| Ubersuggest (related) | 1,247 | 4,261 | 5,811.0 | ||
| sale car | Google Keyword Planner | 2,373 | 691 | 5,811.0 | |
| Ahrefs (all keyword ideas) | 293,908 | 43,488 | 5,811.0 | ||
| Ahrefs (phrase match) | 5,811 | 23,021 | 5,811.0 | ||
| SEMrush (broad match) | 453,534 | 36,523 | 5,811.0 | ||
| SEMrush (phrase match) | 113,664 | 25,475 | 5,811.0 | ||
| Ubersuggest (related) | 526 | 4,261 | 5,811.0 | ||
| automotive technician | Google Keyword Planner | 200 | 691 | 5,811.0 | |
| Ahrefs (all keyword ideas) | 8,834 | 43,488 | 5,811.0 | ||
| Ahrefs (phrase match) | 4,396 | 23,021 | 5,811.0 | ||
| SEMrush (broad match) | 3,950 | 36,523 | 5,811.0 | ||
| SEMrush (phrase match) | 3,607 | 25,475 | 5,811.0 | ||
| Ubersuggest (related) | 669 | 4,261 | 5,811.0 | ||
| dealer | Google Keyword Planner | 456 | 691 | 5,811.0 | |
| Ahrefs (all keyword ideas) | 1,052,918 | 43,488 | 5,811.0 | ||
| Ahrefs (phrase match) | 1,037,053 | 23,021 | 5,811.0 | ||
| SEMrush (broad match) | 1,246,425 | 36,523 | 5,811.0 | ||
| SEMrush (phrase match) | 642,171 | 25,475 | 5,811.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 5,811.0 | ||
| Ubersuggest (related) | 152,426 | 4,261 | 5,811.0 | ||
| Retail | what is retail | Google Keyword Planner | 184 | 691 | 2,985.0 |
| Ahrefs (all keyword ideas) | 17,705 | 43,488 | 2,985.0 | ||
| Ahrefs (phrase match) | 2,370 | 23,021 | 2,985.0 | ||
| SEMrush (broad match) | 8,567 | 36,523 | 2,985.0 | ||
| SEMrush (phrase match) | 7,505 | 25,475 | 2,985.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,985.0 | ||
| Ubersuggest (related) | 315 | 4,261 | 2,985.0 | ||
| retail marketing | Google Keyword Planner | 293 | 691 | 2,985.0 | |
| Ahrefs (all keyword ideas) | 7,453 | 43,488 | 2,985.0 | ||
| Ahrefs (phrase match) | 2,985 | 23,021 | 2,985.0 | ||
| SEMrush (broad match) | 11,410 | 36,523 | 2,985.0 | ||
| SEMrush (phrase match) | 4,126 | 25,475 | 2,985.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,985.0 | ||
| Ubersuggest (related) | 817 | 4,261 | 2,985.0 | ||
| promo code | Google Keyword Planner | 2,139 | 691 | 2,985.0 | |
| Ahrefs (all keyword ideas) | 908,287 | 43,488 | 2,985.0 | ||
| Ahrefs (phrase match) | 855,277 | 23,021 | 2,985.0 | ||
| SEMrush (broad match) | 860,316 | 36,523 | 2,985.0 | ||
| SEMrush (phrase match) | 758,291 | 25,475 | 2,985.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,985.0 | ||
| Ubersuggest (related) | 184,168 | 4,261 | 2,985.0 | ||
| voucher codes | Google Keyword Planner | 1,546 | 691 | 2,985.0 | |
| Ahrefs (all keyword ideas) | 11,707 | 43,488 | 2,985.0 | ||
| Ahrefs (phrase match) | 8,562 | 23,021 | 2,985.0 | ||
| SEMrush (broad match) | 62,541 | 36,523 | 2,985.0 | ||
| SEMrush (phrase match) | 15,422 | 25,475 | 2,985.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,985.0 | ||
| Ubersuggest (related) | 4,487 | 4,261 | 2,985.0 | ||
| retail trade | Google Keyword Planner | 22 | 691 | 2,985.0 | |
| Ahrefs (all keyword ideas) | 5,067 | 43,488 | 2,985.0 | ||
| Ahrefs (phrase match) | 1,352 | 23,021 | 2,985.0 | ||
| SEMrush (broad match) | 2,652 | 36,523 | 2,985.0 | ||
| SEMrush (phrase match) | 1,662 | 25,475 | 2,985.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 2,985.0 | ||
| Ubersuggest (related) | 218 | 4,261 | 2,985.0 | ||
| Solar Energy | solar panels | Google Keyword Planner | 916 | 691 | 4,710.0 |
| Ahrefs (all keyword ideas) | 168,715 | 43,488 | 4,710.0 | ||
| Ahrefs (phrase match) | 155,362 | 23,021 | 4,710.0 | ||
| SEMrush (broad match) | 185,332 | 36,523 | 4,710.0 | ||
| SEMrush (phrase match) | 86,195 | 25,475 | 4,710.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 4,710.0 | ||
| Ubersuggest (related) | 21,147 | 4,261 | 4,710.0 | ||
| solar battery charger | Google Keyword Planner | 797 | 691 | 4,710.0 | |
| Ahrefs (all keyword ideas) | 12,707 | 43,488 | 4,710.0 | ||
| Ahrefs (phrase match) | 3,223 | 23,021 | 4,710.0 | ||
| SEMrush (broad match) | 5,377 | 36,523 | 4,710.0 | ||
| SEMrush (phrase match) | 5,017 | 25,475 | 4,710.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 4,710.0 | ||
| Ubersuggest (related) | 634 | 4,261 | 4,710.0 | ||
| solar panel price | Google Keyword Planner | 1,096 | 691 | 4,710.0 | |
| Ahrefs (all keyword ideas) | 11,032 | 43,488 | 4,710.0 | ||
| Ahrefs (phrase match) | 1,753 | 23,021 | 4,710.0 | ||
| SEMrush (broad match) | 4,710 | 36,523 | 4,710.0 | ||
| SEMrush (phrase match) | 3,171 | 25,475 | 4,710.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 4,710.0 | ||
| Ubersuggest (related) | 1,058 | 4,261 | 4,710.0 | ||
| free energy | Google Keyword Planner | 578 | 691 | 4,710.0 | |
| Ahrefs (all keyword ideas) | 59,682 | 43,488 | 4,710.0 | ||
| Ahrefs (phrase match) | 46,123 | 23,021 | 4,710.0 | ||
| SEMrush (broad match) | 29,545 | 36,523 | 4,710.0 | ||
| SEMrush (phrase match) | 28,911 | 25,475 | 4,710.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 4,710.0 | ||
| Ubersuggest (related) | 2,987 | 4,261 | 4,710.0 | ||
| sunpower | Google Keyword Planner | 388 | 691 | 4,710.0 | |
| Ahrefs (all keyword ideas) | 16,317 | 43,488 | 4,710.0 | ||
| Ahrefs (phrase match) | 16,317 | 23,021 | 4,710.0 | ||
| SEMrush (broad match) | 9,473 | 36,523 | 4,710.0 | ||
| SEMrush (phrase match) | 9,212 | 25,475 | 4,710.0 | ||
| Moz (mix of sources) | 1,000 | 1,000 | 4,710.0 | ||
| Ubersuggest (related) | 1,631 | 4,261 | 4,710.0 |
To get a sense of the data, we are looking at the distribution of suggestions per SEO tool and keyword category.
df_kw %>%
ggplot(aes(suggested)) +
geom_histogram(aes(color = tool_fct, fill = tool_fct), binwidth = 300000) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_cartesian(xlim = c(range_kw[1], range_kw[2])) +
scale_x_continuous(labels = num_format, expand = c(.08, .08)) +
scale_color_carto_d(palette = "Prism", guide = F) +
scale_fill_carto_d(palette = "Prism", guide = F) +
labs(x = "Keywords Suggestions", y = "Count",
title = "**Counts of Keyword Suggestions by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))df_kw %>%
ggplot(aes(suggested)) +
geom_histogram(aes(color = category_fct, fill = category_fct), binwidth = 300000) +
facet_wrap(~ category, scales = "free_x", ncol = 5) +
coord_cartesian(xlim = c(range_kw[1], range_kw[2])) +
scale_x_continuous(labels = num_format, expand = c(.1, .1),
breaks = c(0, 3000000, 6000000)) +
scale_color_simpsons(guide = F) +
scale_fill_simpsons(guide = F) +
labs(x = "Keywords Suggestions", y = "Count",
title = "**Counts of Keyword Suggestions by Keyword Category**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))Since the data is left-skewed (i.e. many more observations with low values compared to higher ones), we are going to use a logarithmic scale for the histograms and most of the following plots.
df_kw %>%
ggplot(aes(suggested)) +
geom_histogram(aes(color = tool_fct, fill = tool_fct), binwidth = .4) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_cartesian(xlim = c(1, range_kw[2] + 10000000)) +
scale_x_log10(labels = num_format) +
scale_color_carto_d(palette = "Prism", guide = F) +
scale_fill_carto_d(palette = "Prism", guide = F) +
labs(x = "Keywords suggested", y = "Count",
title = "**Counts of Keyword Suggestions by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))Key takeaways:
Google Keyword Planner reaches very low levels of suggestions with a mean around 1,000.
Ahrefs and SEMrush seem to reach the highest amount of suggestions.
Moz only returns a maximum of 1,000 sugestions.
df_kw %>%
ggplot(aes(suggested)) +
geom_histogram(aes(color = category_fct, fill = category_fct), binwidth = .4) +
facet_wrap(~ category, scales = "free_x", ncol = 5) +
coord_cartesian(xlim = c(1, range_kw[2] + 10000000)) +
scale_x_log10(labels = num_format) +
scale_color_simpsons(guide = F) +
scale_fill_simpsons(guide = F) +
labs(x = "Keywords suggested", y = "Count",
title = "**Counts of Keyword Suggestions by Keyword Category**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))Key takeaways:
Distributions of suggested keywords by category range from unimodal (e.g. Dating and Web Hosting) to bimodal (e.g. Financial Services and Vacations Travel)
Highest range are reached by the keywords Financial Services, Online Marketing, and Women Fashion.
In the following, we are looking at each search, mapped to SEO tools and keyword categories. The grey, large dot represents the median of each group, here SEO tool which are ranked by this value:
## jitter per tool
df_kw %>%
ggplot(aes(tool_fct, suggested)) +
stat_summary(fun.y = median, geom = "point",
size = 8, color = "grey70") +
geom_jitter(aes(color = category_fct), alpha = .7, width = .3) +
coord_flip() +
scale_y_continuous(labels = num_format, expand = c(.02, .02)) +
scale_color_simpsons(name = "Keyword Category:") +
guides(color = guide_legend(override.aes = list(size = 5))) +
labs(x = NULL, y = "Keyword Suggestions",
title = "**Each Search by SEO Tool**",
subtitle = "(tools ranked by overall median)") +
theme_flip +
theme(axis.text.y = element_text(size = 13))Key takeaways:
This plot highlights how skewed the data is - it might be useful to show the large differences between some SEO tools in combination with specific keyword categories(with a focus on SEO tools).
Even though the dots were jittered, overplotting makes it hard to distinguish each data point in the low-value-area - a log scale helps here.
Clear ranking when sorted by overall median: Indeed, Ahrefs and SEMrush suggest the highest values, followed by Ubersuggest, Moz, and Google Keyword Planner.
## jitter per tool
df_kw %>%
ggplot(aes(category_fct, suggested)) +
stat_summary(fun.y = median, geom = "point",
size = 6, color = "grey70") +
#geom_jitter(aes(color = tool_fct), alpha = .7, width = .3) +
geom_quasirandom(aes(color = tool_fct),
alpha = .7, size = 1.5,
width = .3, bandwidth = .3, varwidth = T) +
coord_flip() +
scale_y_continuous(labels = num_format, expand = c(.02, .02)) +
scale_color_carto_d(palette = "Prism", name = "SEO Tool:") +
guides(color = guide_legend(override.aes = list(size = 5))) +
labs(x = NULL, y = "Keyword Suggestions",
title = "**Each Search by Keyword Category**",
subtitle = "(categories ranked by overall median)") +
theme_flip +
theme(axis.text.y = element_text(size = 13))Key takeaways:
Indeed, Financial Services, Women Fashion, and Online Marketing gather the most suggestions (ranked 1, 2, and 3 on the y axis), followed by Home Improvement, Diet, Automotive, and Retail.
Plot highlights how kewed the data is in terms of keyword categories and SEO tools (with a focus on categories).
To see more of the pattern, we transform the x axis to a logarithmic scale (as before with the histograms); here, we use so-called beeswarm plots to make the distribution more clear to the reader:
## with log scale
df_kw %>%
group_by(tool) %>%
mutate(median = median(log10(suggested))) %>%
ggplot(aes(fct_reorder(tool, median), suggested)) +
stat_summary(fun.y = median, geom = "point",
size = 6, color = "grey70") +
geom_quasirandom(aes(color = category_fct),
alpha = .7, size = 1.5,
width = .3, bandwidth = .3, varwidth = T) +
coord_flip() +
scale_y_log10(labels = num_format) +
scale_color_simpsons(name = "Keyword Category:") +
guides(color = guide_legend(override.aes = list(size = 5))) +
labs(x = NULL, y = "Keyword Suggestions",
title = "**Each Search by SEO Tool** (logarithmic scale)",
subtitle = "(tools ranked by overall median)") +
theme_flip +
theme(axis.text.y = element_text(size = 13))Key takeaways:
## with log scale
df_kw %>%
ggplot(aes(category_fct, suggested)) +
stat_summary(fun.y = median, geom = "point",
size = 7, color = "grey70") +
geom_beeswarm(aes(color = tool_fct),
alpha = .7, size = 1.5, cex = .7) +
coord_flip() +
scale_y_log10(labels = num_format) +
scale_color_carto_d(palette = "Prism", name = "SEO Tool:") +
guides(color = guide_legend(override.aes = list(size = 5))) +
labs(x = NULL, y = "Keyword Suggestions",
title = "**Each Search by Keyword Category** (logarithmic scale)",
subtitle = "(cateogries ranked by overall median)") +
theme_flip +
theme(axis.text.y = element_text(size = 15),
plot.margin = margin(r = 20))Key takeaways:
Using small multiples, we can visualize the pattern of the raw data per tool and keyword category at the same time:
Distributions can be effectively summarized as box and whiskers plots which indicate the median (thick line), the interquartile range (box), the minimum and maximum excluding outliers (whiskers), and outliers (points):
df_kw %>%
ggplot(aes(category_fct, suggested)) +
geom_boxplot(aes(color = category_fct), outlier.size = 1) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_flip() +
scale_y_log10(labels = num_format,
limits = c(range_kw[1], range_kw[2] + 10000000)) +
scale_color_simpsons(guide = F) +
labs(x = NULL, y = "Keyword Suggestions",
title = "**Keyword Categories by SEO Tool**",
subtitle = "(categories ranked by overall median)") +
theme_flip +
theme_facetKey takeaways:
Trends amoung keyword categories are more or less the same besides Google Keyword Planner and Moz which suggest around/always 1,000.
Ahrefs seems to be the most consistent tool in terms of variability among categories.
df_kw %>%
ggplot(aes(tool_fct, suggested)) +
geom_boxplot(aes(color = tool_fct), outlier.size = 1) +
facet_wrap(~ category, scales = "free_x", ncol = 5) +
coord_flip() +
scale_y_log10(labels = num_format,
limits = c(range_kw[1], range_kw[2] + 10000000)) +
scale_color_carto_d(palette = "Prism", guide = F) +
labs(x = NULL, y = "Keyword Suggestions",
title = "**SEO Tool by Keyword Categories**",
subtitle = "(tools ranked by overall median)") +
theme_flip +
theme_facetKey takeaways:
Ahrefs seems to be more competitive in the categories Home Improvement, Legal, Office Supplies, Online Marketing, Solar Energy, Vacations Travel, and Web Hosting when compared to SEMrush and the other SEO tools.
SEMrush on the other hand seems to suggest slightly more when keywords were related to Dating and Wedding and about the same amount as Ahrefs for Gardening and Women Fashion.
The pattern might be easier to read as linerange plot by showing the minimum-maximum range (line) and the median (dot):
df_kw %>%
group_by(category, tool) %>%
mutate(
min = min(suggested),
max = max(suggested)
) %>%
ungroup() %>%
ggplot(aes(category_fct, suggested, color = category_fct)) +
geom_linerange(aes(ymin = min, ymax = max)) +
stat_summary(fun.y = median, geom = "point", size = 3) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_flip() +
scale_y_log10(labels = num_format,
limits = c(range_kw[1], range_kw[2] + 10000000)) +
scale_color_simpsons(guide = F) +
labs(x = NULL, y = "Keyword Suggestions",
title = "**Keyword Categories by SEO Tool**",
subtitle = "(categories ranked by overall median)") +
theme_flip +
theme_facetdf_kw %>%
group_by(category, tool) %>%
mutate(
min = min(suggested),
max = max(suggested)
) %>%
ungroup() %>%
ggplot(aes(tool_fct, suggested, color = tool_fct)) +
geom_linerange(aes(ymin = min, ymax = max)) +
stat_summary(fun.y = median, geom = "point", size = 3) +
facet_wrap(~ category, scales = "free_x", ncol = 5) +
scale_y_log10(labels = num_format,
limits = c(range_kw[1], range_kw[2] + 10000000)) +
scale_color_carto_d(palette = "Prism", guide = F) +coord_flip() +
labs(x = NULL, y = "Keyword Suggestions",
title = "**Keyword Categories by SEO Tool**",
subtitle = "(tools ranked by overall median)") +
theme_flip +
theme_facetdf_kw %>%
filter(!str_detect(keyword, "All 5 KW")) %>%
mutate(length = nchar(keyword)) %>%
ggplot(aes(length, suggested, color = tool_fct, fill = tool_fct)) +
geom_jitter(width = .1, alpha = .4) +
geom_smooth(method = "lm", color = "grey7", size = .8, alpha = .2) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
scale_x_continuous(limits = c(1, 25),
breaks = c(1, seq(5, 25, by = 5))) +
scale_y_log10(labels = num_format) +
scale_color_carto_d(palette = "Prism", guide = F) +
scale_fill_carto_d(palette = "Prism", guide = F) +
labs(x = "Number of Keyword Characters",
y = "Keyword Suggestions",
title = "**Trends in Keyword Length per SEO Tool**") +
theme_facetif(save == T){ ggsave(here::here("plots", "2_KWS_5_length_tool_lm.png"), width = 14, height = 7, dpi = 300) }
df_kw %>%
filter(!str_detect(keyword, "All 5 KW")) %>%
mutate(length = nchar(keyword)) %>%
ggplot(aes(length, suggested, color = tool_fct, fill = tool_fct)) +
geom_jitter(width = .1, alpha = .4) +
geom_smooth(color = "grey7", size = .8, alpha = .2) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
scale_x_continuous(limits = c(1, 25),
breaks = c(1, seq(5, 25, by = 5))) +
scale_y_log10(labels = num_format) +
scale_color_carto_d(palette = "Prism", guide = F) +
scale_fill_carto_d(palette = "Prism", guide = F) +
labs(x = "Number of Keyword Characters",
y = "Keyword Suggestions",
title = "**Trends in Keyword Length per SEO Tool**") +
theme_facetKey takeaways:
df_kw %>%
filter(!str_detect(keyword, "All 5 KW")) %>%
mutate(length = nchar(keyword)) %>%
ggplot(aes(length, suggested, color = category_fct, fill = category_fct)) +
geom_jitter(width = .1, alpha = .4, size = 2.5) +
geom_smooth(method = "lm", color = "grey7", size = .8, alpha = .2) +
facet_wrap(~ category, scales = "free_x", ncol = 5) +
scale_x_continuous(limits = c(1, 25),
breaks = c(1, seq(5, 25, by = 5))) +
scale_y_log10(labels = num_format) +
scale_color_simpsons(guide = F) +
scale_fill_simpsons(guide = F) +
labs(x = "Number of Keyword Characters",
y = "Keyword Suggestions",
title = "**Trends in Keyword Length per Keyword Category**") +
theme_facetif(save == T){ ggsave(here::here("plots", "2_KWS_5_length_category_lm.png"), width = 14, height = 10, dpi = 300) }
df_kw %>%
filter(!str_detect(keyword, "All 5 KW")) %>%
mutate(length = nchar(keyword)) %>%
ggplot(aes(length, suggested, color = category_fct, fill = category_fct)) +
geom_jitter(width = .1, alpha = .4, size = 2.5) +
geom_smooth(color = "grey7", size = .8, alpha = .2) +
facet_wrap(~ category, scales = "free_x", ncol = 5) +
scale_x_continuous(limits = c(1, 25),
breaks = c(1, seq(5, 25, by = 5))) +
scale_y_log10(labels = num_format) +
scale_color_simpsons(guide = F) +
scale_fill_simpsons(guide = F) +
labs(x = "Number of Keyword Characters",
y = "Keyword Suggestions",
title = "**Trends in Keyword Length per Keyword Category**") +
theme_facetKey takeaways:
In the following, we investigate the data in a more detailled manner: each keyword, encoded by SEO tool and keyword category.
df_kw %>%
group_by(category, keyword) %>%
mutate(sum = sum(suggested)) %>%
ungroup() %>%
ggplot(aes(fct_reorder(tool_fct, -tool_median),
fct_reorder(keyword, sum),
size = suggested, fill = category_fct)) +
geom_point(shape = 21, color = "grey20", stroke = .8) +
scale_x_discrete(position = "top") +
scale_y_discrete(expand = c(.01, .01)) +
scale_fill_simpsons(name = "Keyword Category:") +
scale_size(name = "Keyword Suggestions:",
range = c(2, 10),
labels = num_format,
breaks = c(500, 5000, 50000, 500000, 5000000)) +
guides(
size = guide_legend(override.aes = list(fill = "grey70")),
fill = guide_legend(override.aes = list(size = 5), reverse = T)
) +
labs(x = NULL, y = NULL,
title = "**Performance of All Keywords**",
subtitle = "(tool ranked by overall median, keywords ranked by total suggestions)\n") +
theme_facet +
theme(axis.line.x = element_blank(),
axis.text.x = element_text(size = 13, face = "bold", angle = 30,
vjust = 0, hjust = 0),
axis.ticks.x = element_blank(),
axis.text.y = element_text(size = 12),
legend.key.height = unit(25, "pt"),
legend.position = c(1.3, .75),
plot.margin = margin(r = 200))df_kw %>%
group_by(category, keyword) %>%
mutate(sum = sum(suggested)) %>%
arrange(-sum) %>%
group_by(tool) %>%
mutate(rank = row_number()) %>%
group_by(keyword) %>%
mutate(rank = min(rank)) %>%
ungroup() %>%
filter(rank <= 25) %>%
ggplot(aes(fct_reorder(tool_fct, -tool_median),
fct_reorder(keyword, sum),
size = suggested, fill = category_fct)) +
geom_point(shape = 21, color = "grey20", stroke = .8) +
scale_x_discrete(position = "top") +
scale_y_discrete(expand = c(.02, .02)) +
scale_fill_simpsons(name = "Keyword Category:", limits = levels(df_kw$category_fct)) +
scale_size(name = "Keyword Suggestions:",
range = c(2, 10),
labels = num_format,
breaks = c(500, 5000, 50000, 500000, 5000000)) +
guides(
size = guide_legend(override.aes = list(fill = "grey70")),
fill = guide_legend(override.aes = list(size = 5), reverse = T)
) +
labs(x = NULL, y = NULL,
title = "**Performance of Top 25 Keywords**",
subtitle = "(tool ranked by overall median, keywords ranked by total suggestions)\n") +
theme_facet +
theme(axis.line.x = element_blank(),
axis.text.x = element_text(size = 13, face = "bold", angle = 30,
vjust = 0, hjust = 0),
axis.ticks.x = element_blank(),
axis.text.y = element_text(size = 12),
legend.key.height = unit(25, "pt"),
legend.position = c(1.3, .55),
plot.margin = margin(r = 200))Key takeaways:
Multiples could be ordered by ranking of SEO tools (currently as in csv) and/or keyword categories (curently alphabetic) as well.
Median values and/or ranking could be replaced by mean (note: mean may differ between linear and logarithmic scale)
df_vol <-
df_tools %>%
dplyr::select(tool, keyword, volume) %>%
filter(!is.na(volume)) %>%
group_by(tool) %>%
mutate(tool_median = median(volume)) %>%
ungroup() %>%
mutate(tool_fct = fct_reorder(tool, tool_median))
## range of volume
range_vol <- range(df_vol$volume)
## summary table
df_vol %>%
dplyr::select(
`SEO Tool` = "tool",
volume
) %>%
group_by(`SEO Tool`) %>%
summarize(
Minimum = min(volume, na.rm = T),
Average = round(mean(volume, na.rm = T), 1),
Median = median(volume, na.rm = T),
Maximum = max(volume, na.rm = T),
NAs = sum(is.na(volume))
) %>%
kable(format.args = list(big.mark = ",",
small.mark = ",",
decimal.mark = ".",
scientific = F)) %>%
kable_styling() %>%
column_spec(1, extra_css = "vertical- align:middle;")| SEO Tool | Minimum | Average | Median | Maximum | NAs |
|---|---|---|---|---|---|
| Google Keyword Planner | 110 | 15,555.0 | 3,600 | 1,500,000 | 0 |
| Ahrefs | 0 | 9,608.8 | 2,200 | 1,780,000 | 0 |
| SEMrush | 20 | 15,549.2 | 3,600 | 1,830,000 | 0 |
| KeywordTool.io | 90 | 23,483.4 | 4,400 | 6,120,000 | 0 |
| KWFinder | 0 | 14,927.1 | 3,300 | 1,390,000 | 0 |
| LongtailPro | 90 | 15,888.6 | 3,600 | 1,500,000 | 0 |
| SECockpit | 70 | 15,694.7 | 3,600 | 1,500,000 | 0 |
| Sixtrix | 25 | 26,385.1 | 10,000 | 250,000 | 0 |
df_vol %>%
group_by(tool) %>%
summarize(n = n()) %>%
ggplot(aes(fct_reorder(tool, n), n,
fill = tool)) +
geom_col(color = NA, width = .7) +
geom_hline(yintercept = seq(0, 10000, by = 2500),
color = "white", size = .5) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(0, 10100),
expand = c(.01, .01)) +
scale_fill_carto_d(palette = "Bold", guide = F) +
labs(x = NULL, y = "Keywords",
title = "**Number of Keywords per SEO Tool**") +
theme_flip +
theme(panel.grid.major.x = element_blank(),
axis.text.x = element_text(size = 8))df_vol %>%
ggplot(aes(volume)) +
geom_histogram(aes(color = tool, fill = tool), binwidth = 200000) +
facet_wrap(~ tool, scales = "free_x", nrow = 3) +
coord_cartesian(xlim = c(range_vol[1], range_vol[2])) +
scale_x_continuous(labels = num_format, expand = c(.1, .1)) +
scale_color_carto_d(palette = "Bold", guide = F) +
scale_fill_carto_d(palette = "Bold", guide = F) +
labs(x = "Search Volume", y = "Count",
title = "**Distribution of Search Volume by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))df_vol %>%
ggplot(aes(volume)) +
geom_histogram(aes(color = tool, fill = tool), binwidth = .35) +
facet_wrap(~ tool, scales = "free_x", nrow = 3) +
coord_cartesian(xlim = c(1.9, range_vol[2] + 5000000)) +
scale_x_log10(labels = num_format) +
scale_color_carto_d(palette = "Bold", guide = F) +
scale_fill_carto_d(palette = "Bold", guide = F) +
labs(x = "Search Volume", y = "Count",
title = "**Distribution of Search Volume by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))df_vol %>%
ggplot(aes(tool_fct, volume,
color = tool,)) +
geom_violin(aes(fill = tool),
size = .6, alpha = .2,
width = 1.05, trim = F) +
geom_boxplot(size = .6, width = .15,
coef = 0, outlier.color = NA) +
coord_flip() +
scale_y_log10(labels = num_format,
limits = c(2, range_vol[2]),
expand = c(.03, .03)) +
scale_color_carto_d(palette = "Bold", guide = F) +
scale_fill_carto_d(palette = "Bold", guide = F) +
labs(x = NULL, y = "Search Volume",
title = "**Search Volumes by SEO Tool**",
subtitle = "(tools ranked by overall median)") +
theme_flip +
theme(#axis.title.x = element_text(size = 16),
axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 16))df_keywords_top <-
df_tools_long %>%
group_by(tool, category) %>%
arrange(-value) %>%
mutate(
rank = row_number(),
max_rank = max(rank)
) %>%
filter(rank <= 10) %>%
ungroup()
theme_heat <-
theme(axis.line.x = element_blank(),
axis.line.y = element_blank(),
axis.text.x = element_text(size = 12, angle = 30,
vjust = 0, hjust = 0),
axis.text.y = element_text(size = 11, hjust = 0),
axis.ticks = element_blank(),
plot.margin = margin(5.5, 22, 5.5, 5.5))
## heatmap top volume
df_keywords_top %>%
filter(
category == "volume",
!is.na(value)
) %>%
mutate(
mio = round(value / 1000000, 2),
value_log = log10(value),
font_col = if_else(value <= max(value) - ((max(value) - min(value)) / 2), "light", "dark"),
label = glue::glue("{str_wrap(keyword, 15)}\n{mio}M")
) %>%
#mutate(keyword = fct_reorder(keyword, desc(rank_total))) %>%
ggplot(aes(tool, rank, fill = value_log, label = label)) +
geom_tile(color = "white", size = .5) +
geom_text(aes(color = font_col),
family = "Montserrat", fontface = "bold",
size = 3.3, lineheight = .95) +
scale_x_discrete(expand = c(0, 0), position = "top") +
scale_y_reverse(expand = c(0, 0), breaks = 1:10,
labels = glue::glue("Rank {1:10}" )) +
scale_color_manual(values = c('dark' = 'black', 'light' = 'white'),
guide = F) +
scico::scale_fill_scico(palette = "lapaz", guide = F) +
labs(x = NULL, y = NULL,
title = "**Top 10 Keywords of Each SEO Tool** (with Regard to Search Volume)") +
theme_heatdf_vol %>%
mutate(length = nchar(keyword)) %>%
ggplot(aes(length, volume, color = tool, fill = tool)) +
geom_jitter(width = .2, alpha = .1, size = .5) +
geom_smooth(method = "lm", color = "grey7", size = .4, alpha = .4) +
facet_wrap(~ tool, scales = "free_x", nrow = 3) +
scale_x_continuous(limits = c(1, 60)) +
scale_y_log10(labels = num_format, expand = c(.1, .1)) +
scale_color_carto_d(palette = "Bold", guide = F) +
scale_fill_carto_d(palette = "Bold", guide = F) +
labs(x = "Number of Keyword Characters",
y = "Keyword Suggestions",
title = "**Search Volume in Relation to Keyword Length per SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25))df_diff <-
df_tools %>%
dplyr::select(tool, keyword, diff) %>%
filter(!is.na(diff)) %>%
group_by(tool) %>%
mutate(tool_median = median(diff)) %>%
ungroup() %>%
mutate(tool_fct = fct_reorder(tool, tool_median))
## range of difficulty
range_diff <- range(df_diff$diff)
## summary table
df_diff %>%
dplyr::select(
`SEO Tool` = "tool",
diff
) %>%
group_by(`SEO Tool`) %>%
summarize(
Minimum = round(min(diff, na.rm = T), 2),
Average = round(mean(diff, na.rm = T), 2),
Median = round(median(diff, na.rm = T), 2),
Maximum = round(max(diff, na.rm = T), 2),
NAs = sum(is.na(diff))
) %>%
kable(format.args = list(big.mark = ",",
small.mark = ",",
decimal.mark = ".",
scientific = F)) %>%
kable_styling() %>%
column_spec(1, extra_css = "vertical- align:middle;")| SEO Tool | Minimum | Average | Median | Maximum | NAs |
|---|---|---|---|---|---|
| Ahrefs | 0.00 | 22.54 | 16.00 | 99.00 | 0 |
| SEMrush | 0.00 | 81.17 | 84.12 | 99.81 | 0 |
| KeywordTool.io | 0.00 | 62.62 | 77.00 | 100.00 | 0 |
| KWFinder | 6.00 | 39.14 | 38.00 | 86.00 | 0 |
| LongtailPro | 11.00 | 41.15 | 41.00 | 79.00 | 0 |
| SECockpit | 1.29 | 9.39 | 7.76 | 100.00 | 0 |
| Sixtrix | 10.00 | 50.95 | 50.00 | 85.00 | 0 |
df_diff %>%
group_by(tool) %>%
summarize(n = n()) %>%
ggplot(aes(fct_reorder(tool, n), n,
fill = tool)) +
geom_col(color = NA, width = .7) +
geom_hline(yintercept = seq(0, 10000, by = 2500),
color = "white", size = .5) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(0, 10100),
expand = c(.01, .01)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = NULL, y = "Keywords",
title = "**Number of Keywords per SEO Tool**") +
theme_flip +
theme(panel.grid.major.x = element_blank(),
axis.text.x = element_text(size = 8))df_diff %>%
ggplot(aes(diff)) +
geom_histogram(aes(color = tool, fill = tool), binwidth = 5) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_cartesian(xlim = c(-2, range_diff[2]), expand = c(.1, .1)) +
scale_x_continuous(labels = num_format) +
scale_color_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = "Difficulty Score", y = "Count",
title = "**Distribution of Difficulty Scores by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))df_diff %>%
ggplot(aes(diff)) +
geom_histogram(aes(color = tool, fill = tool), binwidth = .1) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_cartesian(xlim = c(.9, range_diff[2])) +
scale_x_log10(labels = num_format) +
scale_color_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = "Difficulty Score", y = "Count",
title = "**Distribution of Difficulty Scores by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))df_diff %>%
ggplot(aes(tool_fct, diff,
color = tool)) +
#geom_violin(aes(fill = tool),
# alpha = .3, width = 1.4, trim = F) +
#geom_boxplot(width = .1, coef = 0, outlier.color = NA) +
geom_boxplot(width = .7, size = 1, alpha = .3, outlier.size = .8) +
coord_flip() +
scale_y_log10(labels = num_format,
limits = c(.9, range_diff[2])) +
scale_color_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = NULL, y = "Difficulty Score",
title = "**Difficulty Scores by SEO Tool**",
subtitle = "(tools ranked by overall median)") +
theme_flip +
theme(axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 16))## heatmap top difficulty
df_keywords_top %>%
filter(
category == "diff",
!is.na(value)
) %>%
mutate(
value_log = log10(value),
font_col = if_else(value <= max(value) - ((max(value) - min(value)) / 2), "light", "dark"),
label = glue::glue("{str_wrap(keyword, 20)}\n{round(value, 1)}%")
) %>%
ggplot(aes(tool, rank, fill = value_log, label = label)) +
geom_tile(color = "white", size = .5) +
geom_text(aes(color = font_col),
family = "Montserrat", fontface = "bold",
size = 3.5, lineheight = .95) +
scale_x_discrete(expand = c(0, 0), position = "top") +
scale_y_reverse(expand = c(0, 0), breaks = 1:10,
labels = glue::glue("Rank {1:10}" )) +
scale_color_manual(values = c('dark' = 'black', 'light' = 'white'),
guide = F) +
scico::scale_fill_scico(palette = "lapaz", guide = F) +
labs(x = NULL, y = NULL,
title = "**Top 10 Keywords of Each SEO Tool** (with Regard to Difficulty Scores)") +
theme_heatif(save == T){ ggsave(here::here("plots", "4_DIF_3_heatmap_keywords_top.png"), width = 14, height = 8, dpi = 300) }
# ## heatmap bottom volume
# df_keywords_top %>%
# filter(category == "volume", rank < 0) %>%
# mutate(
# value = log10(value),
# max = max(value, na.rm = T),
# min = min(value, na.rm = T),
# font_col = if_else(value <= max(value) - ((max(value) - min(value)) / 2), "light", "dark"),
# label = glue::glue("<b>{keyword}</b><br><span style='font-size:9pt'>{round(value, 2)}M</span>")
# ) %>%
# #mutate(keyword = fct_reorder(keyword, desc(rank_total))) %>%
# ggplot(aes(tool, rank, fill = value, label = str_wrap(label))) +
# geom_tile(color = "white", size = 0.5) +
# geom_richtext(aes(color = font_col),
# family = "Montserrat", size = 3,
# fill = NA, label.color = NA) +
# scale_x_discrete(expand = c(0, 0), position = "top") +
# scale_y_continuous(expand = c(0, 0), breaks = -1:-10,
# labels = glue::glue("Rank {1:10}")) +
# scale_color_manual(values = c('dark' = 'black', 'light' = 'white'),
# guide = F) +
# scico::scale_fill_scico(palette = "turku", guide = F) +
# labs(x = NULL, y = NULL) +
# theme_heatdf_cpc <-
df_tools %>%
dplyr::select(tool, keyword, cpc) %>%
filter(!is.na(cpc)) %>%
group_by(tool) %>%
mutate(tool_median = median(cpc)) %>%
ungroup() %>%
mutate(tool_fct = fct_reorder(tool, tool_median))
## range of cpc
range_cpc <- range(df_cpc$cpc)
## summary table
df_cpc %>%
dplyr::select(
`SEO Tool` = "tool",
cpc
) %>%
group_by(`SEO Tool`) %>%
summarize(
Minimum = round(min(cpc, na.rm = T), 2),
Average = round(mean(cpc, na.rm = T), 2),
Median = round(median(cpc, na.rm = T), 2),
Maximum = round(max(cpc, na.rm = T), 2),
NAs = sum(is.na(cpc))
) %>%
kable(format.args = list(big.mark = ",",
small.mark = ",",
decimal.mark = ".",
scientific = F)) %>%
kable_styling() %>%
column_spec(1, extra_css = "vertical- align:middle;")| SEO Tool | Minimum | Average | Median | Maximum | NAs |
|---|---|---|---|---|---|
| Google Keyword Planner | 0.02 | 5.03 | 2.12 | 749.06 | 0 |
| Ahrefs | 0.01 | 3.63 | 1.60 | 355.00 | 0 |
| SEMrush | 0.01 | 3.12 | 1.38 | 209.10 | 0 |
| KeywordTool.io | 0.02 | 4.00 | 1.60 | 361.05 | 0 |
| KWFinder | 0.02 | 4.46 | 1.66 | 361.05 | 0 |
| LongtailPro | 0.01 | 4.33 | 1.61 | 361.05 | 0 |
| SECockpit | 0.01 | 5.25 | 2.20 | 502.10 | 0 |
| Sixtrix | 0.11 | 5.07 | 1.66 | 195.47 | 0 |
df_cpc %>%
group_by(tool) %>%
summarize(n = n()) %>%
ggplot(aes(fct_reorder(tool, n), n,
fill = tool)) +
geom_col(color = NA, width = .7) +
geom_hline(yintercept = seq(0, 10000, by = 2500),
color = "white", size = .5) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(0, 10100),
expand = c(.01, .01)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = NULL, y = "Keywords",
title = "**Number of Keywords per SEO Tool**") +
theme_flip +
theme(panel.grid.major.x = element_blank(),
axis.text.x = element_text(size = 8))df_cpc %>%
ggplot(aes(cpc)) +
geom_histogram(aes(color = tool, fill = tool), binwidth = 30) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_cartesian(xlim = c(-20, range_cpc[2], expand = c(.1, .1))) +
scale_x_continuous(labels = num_format) +
scale_color_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = "CPC", y = "Count",
title = "**Distribution of Cost per Click (CPC) by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))df_cpc %>%
ggplot(aes(cpc)) +
geom_histogram(aes(color = tool, fill = tool), binwidth = .2) +
facet_wrap(~ tool, scales = "free_x", nrow = 2) +
coord_cartesian(xlim = c(.005, range_cpc[2])) +
scale_x_log10(labels = num_format) +
scale_color_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = "CPC", y = "Count",
title = "**Distribution of Cost per Click (CPC) by SEO Tool**") +
theme_facet +
theme(panel.grid.major.y = element_line(size = .25),
axis.text.x = element_text(size = 8))df_cpc %>%
ggplot(aes(tool_fct, cpc,
color = tool)) +
#geom_violin(aes(fill = tool),
# alpha = .3, width = 1.5, trim = F) +
#geom_boxplot(width = .1, coef = 0, outlier.color = NA) +
geom_boxplot(width = .7, size = 1, alpha = .3, outlier.size = .8) +
coord_flip() +
scale_y_log10(labels = num_format,
limits = c(.01, range_cpc[2])) +
scale_color_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
scale_fill_carto_d(palette = "Bold", guide = F,
limits = levels(df_tools$tool)) +
labs(x = NULL, y = "CPC",
title = "**Cost per Click (CPC) by SEO Tool**",
subtitle = "(tools ranked by overall median)") +
theme_flip +
theme(axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 16))## heatmap top difficulty
df_keywords_top %>%
filter(
category == "cpc",
!is.na(value)
) %>%
mutate(
value_log = log10(value),
font_col = if_else(value <= max(value) - ((max(value) - min(value)) / 2), "light", "dark"),
label = glue::glue("{str_wrap(keyword, 18)}\n${round(value, 0)}")
) %>%
ggplot(aes(tool, rank, fill = value_log, label = label)) +
geom_tile(color = "white", size = .5) +
geom_text(aes(color = font_col),
family = "Montserrat", fontface = "bold",
size = 3.3, lineheight = .95) +
scale_x_discrete(expand = c(0, 0), position = "top") +
scale_y_reverse(expand = c(0, 0), breaks = 1:10,
labels = glue::glue("Rank {1:10}" )) +
scale_color_manual(values = c('dark' = 'black', 'light' = 'white'), guide = F) +
scico::scale_fill_scico(palette = "lapaz", guide = F) +
labs(x = NULL, y = NULL,
title = "**Top 10 Keywords of Each SEO Tool** (with regard to Cost per Click)") +
theme_heatdf_tools %>%
dplyr::select(-tool_id) %>%
pivot_longer(
c(volume, diff, cpc),
names_to = "category",
values_to = "value"
) %>%
group_by(category) %>%
mutate(
avg = mean(value, na.rm = T),
comp = value / avg
) %>%
group_by(tool, category) %>%
summarize(
avg = mean(comp, na.rm = T),
log_avg = log2(avg),
sd = sd(comp, na.rm = T)
) %>%
ggplot(aes(tool, category, fill = log_avg)) +
geom_tile(color = "white", size = 0.8) +
coord_equal() +
scale_x_discrete(position = "top",
expand = c(0, 0)) +
scale_y_discrete(labels = c("Cost per Click (CPC)", "Difficulty Scores", "Search Volume"),
expand = c(0, 0)) +
scico::scale_fill_scico(palette = "cork",
name = "Difference to Overall Average",
limits = c(-1.2, 1.2),
breaks = seq(-1, 1, by = 1),
labels = c("50%", "100%", "200%")) +
labs(x = NULL, y = NULL,
title = "**Comparison of Performance in Volume, Difficulty & CPC**<br>") +
guides(fill = guide_colorbar(barheight = unit(3, units = "mm"),
barwidth = unit(100, units = "mm"),
direction = "horizontal",
ticks.colour = "white",
title.position = "top",
title.hjust = 0.5)) +
theme(axis.line.x = element_blank(),
axis.line.y = element_blank(),
axis.text.x = element_text(size = 12, face = "bold", angle = 30,
vjust = 0, hjust = 0),
axis.text.y = element_text(size = 12, face = "bold"),
axis.ticks = element_blank(),
legend.position = "bottom",
legend.title = element_text(size = 11),
legend.text = element_text(size = 9),
plot.margin = margin(5.5, 22, 5.5, 5.5))## comparison of average vol, diff and cpc
df_tools_ref <-
df_tools_long %>%
filter(tool == "Google Keyword Planner") %>%
group_by(category) %>%
summarize(ref = mean(value, na.rm = T)) %>%
left_join(filter(df_tools_long, tool != "Google Keyword Planner")) %>%
group_by(tool, category) %>%
summarize(difference = mean(value, na.rm = T) - unique(ref)) %>%
ungroup()
theme_lolli <-
theme(axis.title.x = element_text(size = 13, color ="grey20"),
axis.text.x = element_text(size = 11),
axis.text.y = element_blank(),
axis.line.y = element_blank(),
axis.ticks.y = element_blank(),
panel.grid.major.x = element_line(size = 0.6, color = "grey70"),
plot.margin = margin(5, 20, 5, 20),
plot.background = element_rect(fill = NA, color = NA))
## plot search volume
lolli_vol <-
df_tools_ref %>%
filter(category == "volume") %>%
mutate(
tool_fct = fct_reorder(tool, difference),
pos_r = if_else(difference >= 0, difference, NA_real_),
pos_l = if_else(difference < 0, difference, NA_real_)
) %>%
ggplot(aes(tool_fct, difference,
color = tool)) +
geom_segment(aes(x = tool_fct, xend = tool_fct,
y = 0, yend = difference),
size = 1.5) +
geom_hline(yintercept = 0, color = "grey70", size = .9) +
geom_point(size = 5) +
geom_text(aes(tool_fct, pos_r, label = tool_fct),
hjust = 0, nudge_y = 600,
family = "Montserrat",
fontface = "bold", size = 4.7) +
geom_text(aes(tool_fct, pos_l, label = tool_fct),
hjust = 1, nudge_y = -600,
family = "Montserrat",
fontface = "bold", size = 4.7) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(-7000, NA),
expand = c(.3, .3)) +
scale_color_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
scale_fill_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
labs(x = NULL, y = "Difference in Average Search Volume") +
theme_flip +
theme_lolli
## plot difficulty scores
lolli_dif <-
df_tools_ref %>%
filter(category == "diff") %>%
mutate(
tool_fct = fct_reorder(tool, difference),
pos_r = if_else(difference >= 0, difference, NA_real_),
pos_l = if_else(difference < 0, difference, NA_real_)
) %>%
ggplot(aes(tool_fct, difference,
color = tool)) +
geom_segment(aes(x = tool_fct, xend = tool_fct,
y = 0, yend = difference),
size = 1.5) +
geom_hline(yintercept = 0, color = "grey70", size = .9) +
geom_point(size = 5) +
geom_text(aes(tool_fct, pos_r, label = tool_fct),
hjust = 0, nudge_y = 3,
family = "Montserrat",
fontface = "bold", size = 4.7) +
geom_text(aes(tool_fct, pos_l, label = tool_fct),
hjust = 1, nudge_y = -3,
family = "Montserrat",
fontface = "bold", size = 4.7) +
coord_flip() +
scale_y_continuous(labels = num_format,
expand = c(.2, .2)) +
scale_color_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
scale_fill_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
labs(x = NULL, y = "Difference in Average Difficulty Score",
title = "**Performance of SEO Tools in Comparison to Google Keyword Planner** (comparison of averages)") +
theme_flip +
theme_lolli +
theme(plot.title = element_markdown(size = 22, margin = margin(b = 20)))
## plot cost per click
lolli_cpc <-
df_tools_ref %>%
filter(category == "cpc") %>%
mutate(
tool_fct = fct_reorder(tool, difference),
pos_r = if_else(difference >= 0, difference, NA_real_),
pos_l = if_else(difference < 0, difference, NA_real_)
) %>%
ggplot(aes(tool_fct, difference,
color = tool)) +
geom_segment(aes(x = tool_fct, xend = tool_fct,
y = 0, yend = difference),
size = 1.5) +
geom_hline(yintercept = 0, color = "grey70", size = .9) +
geom_point(size = 5) +
geom_text(aes(tool_fct, pos_r, label = tool_fct),
hjust = 0, nudge_y = .1,
family = "Montserrat",
fontface = "bold", size = 4.7) +
geom_text(aes(tool_fct, pos_l, label = tool_fct),
hjust = 1, nudge_y = -.1,
family = "Montserrat",
fontface = "bold", size = 4.7) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(-1.7, .5),
expand = c(.2, .2)) +
scale_color_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
scale_fill_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
labs(x = NULL, y = "Difference in Average Cost per Click (CPC)") +
theme_flip +
theme_lolli
lolli_vol + lolli_dif + lolli_cpc + plot_layout(nrow = 1)## comparison of median vol, diff and cpc
df_tools_ref_m <-
df_tools_long %>%
filter(tool == "Google Keyword Planner") %>%
group_by(category) %>%
summarize(ref = median(value, na.rm = T)) %>%
left_join(filter(df_tools_long, tool != "Google Keyword Planner")) %>%
group_by(tool, category) %>%
summarize(difference = median(value, na.rm = T) - unique(ref)) %>%
ungroup()
## plot search volume
lolli_vol <-
df_tools_ref_m %>%
filter(category == "volume") %>%
mutate(
tool_fct = fct_reorder(tool, difference),
pos_r = if_else(difference >= 0, difference, NA_real_),
pos_l = if_else(difference < 0, difference, NA_real_)
) %>%
ggplot(aes(tool_fct, difference,
color = tool)) +
geom_segment(aes(x = tool_fct, xend = tool_fct,
y = 0, yend = difference),
size = 1.5) +
geom_hline(yintercept = 0, color = "grey70", size = .9) +
geom_point(size = 5) +
geom_text(aes(tool_fct, pos_r, label = tool_fct),
hjust = 0, nudge_y = 400,
family = "Montserrat",
fontface = "bold", size = 4.7) +
geom_text(aes(tool_fct, pos_l, label = tool_fct),
hjust = 1, nudge_y = -400,
family = "Montserrat",
fontface = "bold", size = 4.7) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(-3000, NA),
expand = c(.15, .15)) +
scale_color_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
scale_fill_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
labs(x = NULL, y = "Difference in Median Search Volume") +
theme_flip +
theme_lolli
## plot difficulty scores
lolli_dif <-
df_tools_ref_m %>%
filter(category == "diff") %>%
mutate(
tool_fct = fct_reorder(tool, difference),
pos_r = if_else(difference >= 0, difference, NA_real_),
pos_l = if_else(difference < 0, difference, NA_real_)
) %>%
ggplot(aes(tool_fct, difference,
color = tool)) +
geom_segment(aes(x = tool_fct, xend = tool_fct,
y = 0, yend = difference),
size = 1.5) +
geom_hline(yintercept = 0, color = "grey70", size = .9) +
geom_point(size = 5) +
geom_text(aes(tool_fct, pos_r, label = tool_fct),
hjust = 0, nudge_y = 3,
family = "Montserrat",
fontface = "bold", size = 4.7) +
geom_text(aes(tool_fct, pos_l, label = tool_fct),
hjust = 1, nudge_y = -3,
family = "Montserrat",
fontface = "bold", size = 4.7) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(-80, 45),
expand = c(.1, .1)) +
scale_color_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
scale_fill_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
labs(x = NULL, y = "Difference in Median Difficulty Score",
title = "**Performance of SEO Tools in Comparison to Google Keyword Planner** (comparison of medians)") +
theme_flip +
theme_lolli +
theme(plot.title = element_markdown(size = 22, margin = margin(b = 20)))
## plot cost per click
lolli_cpc <-
df_tools_ref_m %>%
filter(category == "cpc") %>%
mutate(
tool_fct = fct_reorder(tool, difference),
pos_r = if_else(difference >= 0, difference, NA_real_),
pos_l = if_else(difference < 0, difference, NA_real_)
) %>%
ggplot(aes(tool_fct, difference,
color = tool)) +
geom_segment(aes(x = tool_fct, xend = tool_fct,
y = 0, yend = difference),
size = 1.5) +
geom_hline(yintercept = 0, color = "grey70", size = .9) +
geom_point(size = 5) +
geom_text(aes(tool_fct, pos_r, label = tool_fct),
hjust = 0, nudge_y = .08,
family = "Montserrat",
fontface = "bold", size = 4.7) +
geom_text(aes(tool_fct, pos_l, label = tool_fct),
hjust = 1, nudge_y = -.08,
family = "Montserrat",
fontface = "bold", size = 4.7) +
coord_flip() +
scale_y_continuous(labels = num_format,
limits = c(-.9, .3),
expand = c(.2, .2)) +
scale_color_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
scale_fill_carto_d(palette = "Bold",
limits = levels(df_tools$tool),
guide = F) +
labs(x = NULL, y = "Difference in Median Cost per Click (CPC)") +
theme_flip +
theme_lolli
lolli_vol + lolli_dif + lolli_cpc + plot_layout(nrow = 1)df_keywords_top <-
df_tools_long %>%
group_by(category, keyword) %>%
summarize(avg = mean(value, na.rm = T)) %>%
group_by(category) %>%
arrange(-avg) %>%
mutate(rank_total = row_number()) %>%
filter(rank_total <= 10 | rank_total > (max(rank_total) - 10))
df_keywords_rank <-
df_tools_long %>%
inner_join(df_keywords_top) %>%
#filter(keyword %in% df_keywords_top$keyword) %>%
group_by(category) %>%
arrange(-value) %>%
mutate(rank_tool = row_number())
## heatmap volume
df_keywords_rank %>%
filter(category == "volume", rank_total <= 10) %>%
mutate(keyword = fct_reorder(keyword, desc(rank_total))) %>%
ggplot(aes(tool, keyword, fill = value)) +
geom_tile()Session Info
## R version 3.6.2 (2019-12-12)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 17763)
##
## Matrix products: default
##
## locale:
## [1] LC_COLLATE=German_Germany.1252 LC_CTYPE=German_Germany.1252
## [3] LC_MONETARY=German_Germany.1252 LC_NUMERIC=C
## [5] LC_TIME=German_Germany.1252
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] patchwork_1.0.0.9000 ggsci_2.9 rcartocolor_2.0.0
## [4] kableExtra_1.1.0 ggtext_0.1.0 ggbeeswarm_0.6.0
## [7] showtext_0.7 showtextdb_2.0 sysfonts_0.8
## [10] janitor_1.2.0 rmdformats_0.3.6 inspectdf_0.0.7
## [13] DataExplorer_0.8.1 knitr_1.26 fs_1.3.1
## [16] imager_0.41.2 magrittr_1.5 magick_2.2
## [19] glue_1.3.1 treemapify_2.5.3 reshape2_1.4.3
## [22] prettydoc_0.3.1 mblm_0.12.1 sm_2.2-5.6
## [25] DT_0.10 sentimentr_2.7.1 tidytext_0.2.2
## [28] here_0.1 gganimate_1.0.4 scales_1.1.0
## [31] plotly_4.9.1 extrafont_0.17 hrbrthemes_0.7.1
## [34] ggthemes_4.2.0 readxl_1.3.1 forcats_0.4.0
## [37] stringr_1.4.0 dplyr_0.8.3 purrr_0.3.3
## [40] readr_1.3.1 tidyr_1.0.0 tibble_2.1.3
## [43] ggplot2_3.2.1 tidyverse_1.3.0
##
## loaded via a namespace (and not attached):
## [1] backports_1.1.5 systemfonts_0.1.1 plyr_1.8.5 igraph_1.2.4.2
## [5] selectr_0.4-2 lazyeval_0.2.2 SnowballC_0.6.0 digest_0.6.23
## [9] htmltools_0.4.0 tiff_0.1-5 fansi_0.4.0 ggfittext_0.8.1
## [13] modelr_0.1.5 extrafontdb_1.0 prettyunits_1.0.2 jpeg_0.1-8.1
## [17] colorspace_1.4-1 rvest_0.3.5 haven_2.2.0 xfun_0.11
## [21] tcltk_3.6.2 crayon_1.3.4 jsonlite_1.6 zeallot_0.1.0
## [25] gtable_0.3.0 webshot_0.5.2 scico_1.1.0 Rttf2pt1_1.3.7
## [29] DBI_1.0.0 qdapRegex_0.7.2 Rcpp_1.0.3 viridisLite_0.3.0
## [33] progress_1.2.2 gridtext_0.1.0 textclean_0.9.3 htmlwidgets_1.5.1
## [37] httr_1.4.1 ellipsis_0.3.0 pkgconfig_2.0.3 farver_2.0.1
## [41] dbplyr_1.4.2 tidyselect_0.2.5 labeling_0.3 rlang_0.4.2
## [45] munsell_0.5.0 cellranger_1.1.0 tools_3.6.2 cli_2.0.0
## [49] generics_0.0.2 gifski_0.8.6 broom_0.5.2 evaluate_0.14
## [53] yaml_2.2.0 readbitmap_0.1.5 nlme_3.1-142 xml2_1.2.2
## [57] tokenizers_0.2.1 compiler_3.6.2 rstudioapi_0.10 beeswarm_0.2.3
## [61] curl_4.3 png_0.1-7 reprex_0.3.0 syuzhet_1.0.4
## [65] tweenr_1.0.1 stringi_1.4.3 highr_0.8 gdtools_0.2.1
## [69] lattice_0.20-38 Matrix_1.2-18 markdown_1.1 vctrs_0.2.0
## [73] pillar_1.4.2 lifecycle_0.1.0 networkD3_0.4 data.table_1.12.8
## [77] R6_2.4.1 bookdown_0.16 gridExtra_2.3 bmp_0.3
## [81] vipor_0.4.5 janeaustenr_0.1.5 lexicon_1.2.1 assertthat_0.2.1
## [85] rprojroot_1.3-2 withr_2.1.2 parallel_3.6.2 hms_0.5.2
## [89] grid_3.6.2 rmarkdown_2.0 snakecase_0.11.0 lubridate_1.7.4